Prior to this fix the number of users rendered by mentioned_users could equal the number of members in a channel which would be slow but could in more extreme case crash the page and/or server.
Some plugins have names (e.g. discourse-x-yz) that
are totally different from what they are actually called,
and that causes issues when showing them in a sorted way
in the admin plugin list.
Now, we should use the setting category name from client.en.yml
if it exists, otherwise fall back to the name, for sorting.
This is what we do on the client to determine what text to
show for the plugin name as well.
This adds the following chat metrics:
- _chat_open_channels_with_threads_enabled_ — a count of open channels
where threading is enabled.
- _chat_channel_messages_ — a count of messages sent in a chat channel
(i.e. not a personal chat / direct message), within a thread or outside of a thread.
- _chat_threaded_messages_ — a count of messages sent within a thread
in a chat channel (i.e. not a personal chat / direct messages).
- _chat_direct_messages_ — a count of messages sent in a personal chat / direct messages.
The metrics added using the plugin API introduced in 098ab29d,
and extended in d91456fd.
Note that these stats won't be exposed at the `about.json`
and the `site/statistics.json` routes.
Fixes a flaky test by ensuring Capybara finishes loading the topic page before attempting to open chat. The back to forum url relies on a tracked property (previous url), which is set when visiting the topic page.
Why this change?
The assertion does not make use of Capybara's waiting strategy and is
not really testing anything meaningful by asserting for the src of the
img element.
When validating with a dynamic set of values, especially one that might change during runtime, we should use a lambda or a proc to ensure that the validation uses the most up-to-date set of values. This is particularly important when using config.eager_load = true, which can cause some elements to be loaded only once at startup, thus not reflecting changes made at runtime.
This was the root cause of the issues here, as we were adding more ReviewableScore types after initial load through: `register_reviewable_type Chat::ReviewableMessage`
Small visual improvements for chat header on mobile:
- makes the Back to Forum target size slightly narrower
- makes the text color consistent between header and back button (and d-icon)
- makes the chat heading bold
Why this change?
We have been running into flaky tests which seems to be related to
AR transaction problems. However, we are not able to reproduce this
locally and do not have sufficient information on our builds now to
debug the problem.
What does this change do?
Noe the following changes only applies when `ENV["GITHUB_ACTIONS"]` is
present.
This change introduces an RSpec around hook when `capture_log: true` has
been set for a test. The responsibility of the hook is to capture the
ActiveRecord debug logs and print them out.
This change simplifies the layout of our header when chat is open on mobile. The search icon and hamburger menu icons are also hidden and the Discourse logo is replaced by a ← Forum link to make it easier to continue where you left off within the forum (prior to this update the user could only go back to the forum index page).
Why this change?
When running system tests on our CI, we have been occasionally seeing
server errors like:
```
Error encountered while proccessing /stylesheets/desktop_e58cf7f686aab173f9b778797f241913c2833c39.css
NoMethodError: undefined method `+' for nil:NilClass
/__w/discourse/discourse/vendor/bundle/ruby/3.2.0/gems/actionpack-7.0.7/lib/action_dispatch/journey/path/pattern.rb:139:in `[]'
/__w/discourse/discourse/vendor/bundle/ruby/3.2.0/gems/actionpack-7.0.7/lib/action_dispatch/journey/router.rb:127:in `block (2 levels) in find_routes'
/__w/discourse/discourse/vendor/bundle/ruby/3.2.0/gems/actionpack-7.0.7/lib/action_dispatch/journey/router.rb:126:in `each'
/__w/discourse/discourse/vendor/bundle/ruby/3.2.0/gems/actionpack-7.0.7/lib/action_dispatch/journey/router.rb:126:in `each_with_index'
/__w/discourse/discourse/vendor/bundle/ruby/3.2.0/gems/actionpack-7.0.7/lib/action_dispatch/journey/router.rb:126:in `block in find_routes'
/__w/discourse/discourse/vendor/bundle/ruby/3.2.0/gems/actionpack-7.0.7/lib/action_dispatch/journey/router.rb:123:in `map!'
/__w/discourse/discourse/vendor/bundle/ruby/3.2.0/gems/actionpack-7.0.7/lib/action_dispatch/journey/router.rb:123:in `find_routes'
/__w/discourse/discourse/vendor/bundle/ruby/3.2.0/gems/actionpack-7.0.7/lib/action_dispatch/journey/router.rb:32:in `serve'
/__w/discourse/discourse/vendor/bundle/ruby/3.2.0/gems/actionpack-7.0.7/lib/action_dispatch/routing/route_set.rb:852:in `call'
```
While looking through various Rails issues related to the error above, I
came across https://github.com/rails/rails/pull/27647 which is a fix to
fully initialize routes before the first request is handled. However,
the routes are only fully initialize only if `config.eager_load` is set
to `true`. There is no reason why `config.eager_load` shouldn't be `true` in the
CI environment and this is what a new Rails 7.1 app is generated with.
What does this change do?
Enable `config.eager_load` when `env["CI"]` is present
This changes the Plugins link in the admin sidebar to
be a section instead, which then shows all enabled plugin
admin routes (which are custom routes some plugins e.g.
chat define).
This is done via adding some special preloaded data for
all controllers based on AdminController, and also specifically
on Admin::PluginsController, to have the routes loaded without
additional requests on page load.
We just use a cog for all the route icons for now...we don't
have anything better.
Why this change?
This is what `Capybara::Session#quit` does:
```
def quit
@driver.quit if @driver.respond_to? :quit
@document = @driver = nil
@touched = false
@server&.reset_error!
end
```
One notable thing is that it resets server errors which means that any
server errors encountered by a session is cleared. That is not what we
want since it hides errors even though `Capybara.raise_server_errors`
has been set to `true`.
## Back button to navigate out of add-member area
Currently on mobile, once you're in the member area, there is no easy to return to the general settings area, except exiting the settings altogether, which isn't very user friendly. A go-back link solves the problem.
## Styling tweaks
* Removed the background from the leave button
* Added more spacing between the sections on desktop and removed the fixed height for rows
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
This PR is a reworked version of https://github.com/discourse/discourse/pull/24670.
In chat, we need the ability to have several notifications per `chat_mention`.
Currently, we have one_to_one relationship between `chat_mentions` and `notifications`:
d7a09fb08d/plugins/chat/app/models/chat/mention.rb (L9)
We want to have one_to_many relationship. This PR implements that by introducing
a join table between `chat_mentions` and `notifications`.
The main motivation for this is that we want to solve some performance problems
with mentions that we're having now. Let's say a user sends a message with @ all
in a channel with 50 members, we do two things in this case at the moment:
- create 50 chat_mentions
- create 50 notifications
We don't want to change how notifications work in core, but we want to be more
efficient in chat, and create only 1 `chat_mention` which would link to 50 notifications.
Also note, that on the side of notifications, having a lot of notifications is not so
big problem, because notifications processing can be queued.
Apart from improving performance, this change will make the code design better.
Note that I've marked the old `chat_mention.notification_id` column as ignored, but
I'm not deleting it in this PR. We'll delete it later in https://github.com/discourse/discourse/pull/24800.
This new navbar component is used for every navbar in chat, full page or drawer, and any screen.
This commit also uses this opportunity to correctly decouple drawer-routes from full page routes. This will avoid having this kind of properties in components: `@includeHeader={{false}}`. The header is now defined in the parent template using a navbar. Each route has now its own template wrapped in a div of the name of the route, eg: `<div class="c-routes-threads">..</div>`.
The navbar API:
```gjs
<Navbar as |navbar|>
<navbar.BackButton />
<navbar.Title @title="Foo" />
<navbar.ChannelTitle @channel={{@channel}} />
<navbar.Actions as |action|>
<action.CloseThreadButton />
</navbar.Actions>
</navbar>
```
The full list of components is listed in `plugins/chat/assets/javascripts/discourse/components/navbar/index.gjs` and `plugins/chat/assets/javascripts/discourse/components/navbar/actions.gjs`.
Visually the header is not changing much, only in drawer mode the background has been removed.
This commit also introduces a `<List />` component to facilitate rendering lists in chat plugin.
- sticky doesn't work well with overflow: hidden parents. These overflows were used to hide other issues which shouldn't exist anyways. If it causes issues we should fix the root cause.
- our `--header-offset` is changing a lot on safari while scrolling, sometimes with very unexpected value like: negative or very high value, which causes the navbar to appear at unexpected positions for few ms, this commit is using the value of the header on insert and not changing it after, it shouldn't cause any issue.
We can also now control tabindex, and other html attributes, which allows to lick on the thread indicator but not tab to it for example.
By default the tabindex will be 0.
When navigating to /chat/threads we were not closing the side panel. It would not show as the route doesn't support it, but after if we would open a channel from the sidebar it would open the channel with an empty opened sidepanel .
This commit adds a new "My threads" link in sidebar and drawer. This link will open the "/chat/threads" page which contains all threads where the current user is a member. It's ordered by activity (unread and then last message created).
Moreover, the threads list of a channel page is now showing every threads of a channel, and not just the ones where you are a member.
Introduces the concept of image thumbnails in chat, prior to this we uploaded and used full size chat images within channels and direct messages.
The following changes are covered:
- Post processing of image uploads to create the thumbnail within Chat::MessageProcessor
- Extract responsive image ratios into CookedProcessorMixin (used for creating upload variations)
- Add thumbnail to upload serializer from plugin.rb
- Convert chat upload template to glimmer component using .gjs format
- Use thumbnail image within chat upload component (stores full size img in orig-src data attribute)
- Old uploads which don't have thumbnails will fallback to full size images in channels/DMs
- Update Magnific lightbox to use full size image when clicked
- Update Glimmer lightbox to use full size image (enables zooming for chat images)
I took the wrong approach here, need to rethink.
* Revert "FIX: Use Guardian.basic_user instead of new (anon) (#24705)"
This reverts commit 9057272ee2.
* Revert "DEV: Remove unnecessary method_missing from GuardianUser (#24735)"
This reverts commit a5d4bf6dd2.
* Revert "DEV: Improve Guardian devex (#24706)"
This reverts commit 77b6a038ba.
* Revert "FIX: Introduce Guardian::BasicUser for oneboxing checks (#24681)"
This reverts commit de983796e1.
This consistently fails on core now, see
https://github.com/discourse/discourse/actions/runs/7109919490/job/19355591619?pr=24738
Error: QUnit Test Failure: Browser Id 2 - Discourse Chat | Component | chat message collapser images: escapes link
not ok 444 Chrome 120.0 - [58 ms] - Browser Id 2 - Discourse Chat | Component | chat message collapser images: escapes link
---
actual: >
false
expected: >
true
stack: >
Expected value is %3Cscript%3Esomeeviltitle%3C/script%3E and actual value is
<script>someeviltitle</script>
c.f. de983796e1
There will soon be additional login_required checks
for Guardian, and the intent of many checks by automated
systems is better fulfilled by using BasicUser, which
simulates a logged in TL0 forum user, rather than an
anon user.
In some cases the use of anon still makes sense (e.g.
anonymous_cache), and in that case the more explicit
`Guardian.anon_user` is used
The previous query would look at the existing messages, count them, and update the associated thread.
But, if for some reason messages were **ALL** deleted without updating the `replies_count`, then the query wouldn't find any message, and wouldn't update any thread's `replies_count`.
We're seeing some deprecation warnings in production. This is because we're passing a raw Ruby timestamp, which gets stringified implicitly when written to Redis. As per #15842, this conversion needs to be done explicitly.
This outlet allows to redefine the button displayed when asking the user to join a channel.
The following outletArgs are sent to the outlet:
```
onJoinChannel
channel
icon
title
label
disabled
```
Prior to this fix direct message would always show "Chat in ..." when hovering the channel even if you were hovering a direct message channel with another user (or yourself).
We will now correctly show:
- `Chat in ...` for group channels
- `Chat with ...` for direct message channels
Previously the spec could be flakey as the long message could show on the screen while we await for processing. Now we will first check to have the error message on screen, at this point the erroneous message should never be visible.
In other kind of channels we will only unfollow but for group channels we don't want people to keep appearing in members list.
This commit also creates appropriate services:
- `Chat::LeaveChannel`
- `Chat::UnfollowChannel`
And dedicated endpoint for unfollow: `DELETE /chat/api/channels/:id/memberships/me/follows`
This PR introduces thread support for channel archives. Now, threaded messages are rendered inside a `details` HTML tag in posts.
The transcript markdown rules now support two new attributes: `threadId` and `threadTitle`.
- If `threadId` is present, all nested `chat` tags are rendered inside the first one.
- `threadTitle` (optional) defines the summary content.
```
[chat threadId=19 ... ]
thread OM
[chat ... ]
thread reply
[/chat]
[/chat]
```
If threads are split across multiple posts when archiving, the range of messages in each part will be displayed alongside the thread title. For example: `(message 1 to 16 of 20)` and `(message 17 to 20 of 20)`.
This bug was very reproducible when your last read was a message you didn't read and an admin would delete it. When coming back to the channel you would get a not found, in this case we will now reset last read and present you the last message of the channel.
We could be more fancy and try to detect the next readable message but that would be more code and complexity for such a rare case.
Before this fix we would reset the input two times:
- right before sending message
- and after it's been sent
The second one is actually not necessary, and more over with the server delay the user could have started typing a new message and that would clear it.
Chat will now check for the state of `SiteSetting.private_email` when sending the summary, when enabled, the mail will not display user information, channel information other than the ID and no message information, only the count of messages.
Mentions and other post processing (like images) are still done asynchronously in the background. This should ensure reloading a channel while the message has not been processed yet doesn’t renders a blank message.
As a followup, we could probably simplify the staged message logic, given we have the new cooked on send.
This commit implements drafts for threads by adding a new `thread_id` column to `chat_drafts` table. This column is used to create draft keys on the frontend which are a compound key of the channel and the thread. If the draft is only for the channel, the key will be `c-${channelId}`, if for a thread: `c-${channelId}:t-${threadId}`.
This commit also moves the draft holder from the service to the channel or thread model. The current draft can now always be accessed by doing: `channel.draft` or `thread.draft`.
Other notable changes of this commit:
- moves ChatChannel to gjs
- moves ChatThread to gjs
Fixes a minor issue where "Invalid date" is shown briefly
when sending a message in a chat thread. Change to use
`new Date()` instead like the channel staged message which
does not have this issue.
This PR refactors the following:
* leaving all the CSS applied to the old `modal-body` classes in their respective files
* made new clean styling for `.d-modal` and refactored the template to use the new BEM classes
* `inner-`, `middle-`, `outer-` container classes are gone and replaced with simplified `wrapper` and `container` classes
* use standardised max-sizes with modifiers `-large` and `-max`
* lighter backdrop,
* min-width to prevent puny modals
* other styling changes regarding padding, close button,…
* pulled out all modal overrides into a general `modal-overrides` file + cleanup of outdated CSS
* pulled out login and create account modal styling into their own file, cause it's such a big override
* removed old general login.scss file for mobile & desktop
* only kept some remainders I don't want to touch in `app/assets/stylesheets/common/base/login.scss`
Some of these files are quite small, and if we rename them in the same
commit where we inlined the template, Git may choose to see them as
different files. This commit forces Git to recognize the rename, which
will preserve the lineage of the refactored files.
Follow-up to #24278 that is slightly less trivial.
* Some were "trivial" usages that were missed in the previous PR because the same file that had at least one other non-trivial usage.
* These involve extra arguments or inheritance but I have checked that they seem correct.
- Remove vendored copy
- Update Rails implementation to look for language definitions in node_modules
- Use webpack-based dynamic import for hljs core
- Use browser-native dynamic import for site-specific language bundle (and fallback to webpack-based dynamic import in tests)
- Simplify markdown implementation to allow all languages into the `lang-{blah}` className
- Now that all languages are passed through, resolve aliases at runtime to avoid the need for the pre-built `highlightjs-aliases` index
Group channels will allow users to create channels with a name and invite people. It's possible to add people even after creation of the channel. Removing users is not yet possible but will be added in the near future.
Technically a group channel is `direct_message_channel` with a group attribute set to true on its direct message (chatable). This model might evolve in the future but offers much flexibility for now without having to rely on a complex migration.
The commit essentially consists of:
- a migration to set existing direct message channels with more than 2 users to a group
- a new message creator which allows to search, add members, and create groups
- a new `AddUsersToChannel` service
- a modified `SearchChatable` service
Fixes this problem that happens sometimes in specs:
> Mocha::StubbingError:
> #<Mock:0x135150> was instantiated in one test but it is receiving
invocations within another test. This can lead to unintended
interactions between tests and hence unexpected test failures. Ensure
that every test correctly cleans up any state that it introduces.
The most common thing that we do with fab! is:
fab!(:thing) { Fabricate(:thing) }
This commit adds a shorthand for this which is just simply:
fab!(:thing)
i.e. If you omit the block, then, by default, you'll get a `Fabricate`d object using the fabricator of the same name.
This adds the ability to collect stats without exposing them
among other stats via API.
The most important thing I wanted to achieve is to provide
an API where stats are not exposed by default, and a developer
has to explicitly specify that they should be
exposed (`expose_via_api: true`). Implementing an opposite
solution would be simpler, but that's less safe in terms of
potential security issues.
When working on this, I had to refactor the current solution.
I would go even further with the refactoring, but the next steps
seem to be going too far in changing the solution we have,
and that would also take more time. Two things that can be
improved in the future:
1. Data structures for holding stats can be further improved
2. Core stats are hard-coded in the About template (it's hard
to fix it without correcting data structures first, see point 1):
63a0700d45/app/views/about/index.html.erb (L61-L101)
The most significant refactorings are:
1. Introducing the `Stat` model
2. Aligning the way the core and the plugin stats' are registered
There is an edge case where the following occurs:
1. The user sets a bookmark reminder on a post/topic
2. The post/topic is changed to a PM before or after the reminder
fires, and the notification remains unread by the user
3. The user opens their bookmark reminder notification list
and they can still see the notification even though they cannot
access the topic anymore
There is a very low chance for information leaking here, since
the only thing that could be exposed is the topic title if it
changes to something sensitive.
This commit filters the bookmark unread notifications by using
the bookmarkable can_see? methods and also prevents sending
reminder notifications for bookmarks the user can no longer see.
When quoting a chat message in a post, if that message contains a mention,
that mention should be ignored. But we've been detecting them and sending
notifications to users. This PR fixes the problem. Since this fix is for
the chat plugin, I had to introduce a new API for plugins:
# We strip posts before detecting mentions, oneboxes, attachments etc.
# We strip those elements that shouldn't be detected. For example,
# a mention inside a quote should be ignored, so we strip it off.
# Using this API plugins can register their own post strippers.
def register_post_stripper(&block)
end
Subscriptions manager have been a pain since the beginning, one of the problem is that thread and channels behave mostly the same but with various small difference which I expect to increase over time.
Trying to use subclasses for this case has proven to be a mistake, this commit now uses a class for each case (channel, thread) which for now contains a lot of duplication, which might be reduced in the future but has the merit to make reasoning about each case very simple.
This refactor is fixing a bug introduced in 90efdd7f9d which was causing the wrong channel to be unsubscribed, this shouldn't be possible anymore. We had tests for this which were disabled due to flakeyness, I will consider re-enabling them in the future.
Other notes:
- notices had been added to the subscriptions manager service, they have been moved into their own dedicated service: `ChatChannelNoticesManager`
- the `(each model)` trick used in `<ChatChannel />` since 90efdd7f9d to ensure atomicity has been applied to `<ChatThread />` too
Chat redesign work to improve chat navigation:
- New header title with channel name (thread list on mobile)
- New header title without channel name (thread list on full page chat)
- Removes the close button on threads (mobile only)
- Updates to back button route within thread (mobile), taking user to:
- The thread index, if they accessed the thread from the thread index.
- The channel itself, if they accessed the thread directly from the channel.
- The channel itself, if they accessed the thread from a notification.
- Show thread title in chat drawer header
- Properly convert emoji in thread titles in chat header (all devices)
- Upgrades various templates to use gjs format.
This commit starts from a simple observation: cooking messages on the hot path can be slow. Especially with a lot of mentions.
To move cooking from the hot path, this commit has made the following changes:
- updating cooked, inserting mentions and notifying user of new mentions has been moved inside the `process_message` job. It happens right after the `Chat::MessageProcessor` run, which is where the cooking happens.
- the similar existing code in `rebake!` has also been moved to rely on the `process_message`job only
- refactored `create_mentions` and `update_mentions` into one single `upsert_mentions` which can be called invariably
- allows services to decide if their job is ran inline or later. It avoids to need to know you have to use `Jobs.run_immediately!` in this case, in tests it will be inline per default
- made various frontend changes to make the chat-channel component lifecycle clearer. we had to handle `did-update @channel` which was super awkward and creating bugs with listeners which the changes of the PR made clear in failing specs
- adds a new `-processed` (and `-not-processed`) class on the chat message, this is made to have a good lifecyle hook in system specs
When uploading images, they are assigned a dominant color which gets used in various places, such as Discourse Hub and the new lightbox. Previously in chat we didn't assign this attribute, so it was defaulting to a null value. We did however use it as an inline CSS style for the image background (which is visible while the image is downloaded).
This change adds data-dominant-color to the uploaded image in chat and uses it correctly within lightbox.
This is a follow-up to e6299a3. I additionally fixed these three things:
1. Since e6299a3 there's no need anymore to join the group_users table
when looking for users who were reached by a group mention, so
I removed that join in that commit. But turned out we were joining
the group_users table twice, so I removed the second join in this PR.
That drastically speeded up my test query, from 6 sec to 0.26 sec.
2. We also were joining twice the user_chat_channel_memebership table,
so I removed the second unnecessary join too.
3. We actually need to join the user_chat_channel_memebership table
only in certain cases, and we don't need to do that for group mentions,
so I fixed that too.
As a result of these changes, time of my test query fall down from
6 sec to 0.001 sec. And the resulting SQL query now contains only
one JOIN statement.
A follow-up to faac6773. This PR eliminates one more heavy join by forcing
Active Record to do two queries instead.
Also, along the way, I made this change:
```
# this generates two quries to the groups table
def groups_to_mention
@groups_to_mention = mentionable_groups - groups_with_too_many_members
end
# so I changed it to (this makes only one query to the groups table):
def groups_to_mention
@groups_to_mention ||= mentionable_groups.where("user_count <= ?", SiteSetting.max_users_notified_per_group_mention)
end
```
This one is kind of a premature optimization, because we don't have evidence that
this extra query is a problem, but it seems cleaner this way.
Commits history on this PR may help better understand the change.
Add new chat indicator preference within chat user preferences.
Enabling this option will mean that green notifications will only appear for mentions (within channels and DMs.
This change also enables mentions within direct messages.
- correctly respects min-width/height defined in css
- removes fixed width/height when resizing window
- reduces the min width of the side panel from 250px to 150px
No plugins or themes rely on anonymous_posting_min_trust_level so we
can just switch straight over to anonymous_posting_allowed_groups
This also adds an AUTO_GROUPS const which can be imported in JS
tests which is analogous to the one defined in group.rb. This can be used
to set the current user's groups where JS tests call for checking these groups
against site settings.
Finally a AtLeastOneGroupValidator validator is added for group_list site
settings which ensures that at least one group is always selected, since if
you want to allow all users to use a feature in this way you can just use
the everyone group.
We were incorrectly generating URLs with message id even when it was not provided, resulting in a route ending with "undefined", which was causing an error.
This commit also uses this opportunity to:
- move `invite_users` into a proper controller inside the API namespace
- refactors the code into a service: `Chat::InviteUsersToChannel`
This change allows users to edit their chat messages based on the criteria added to Site Settings.
If the grace period conditions are met then there will be no (edited) text applied to the message.
The following site settings are added to chat:
chat editing grace period (seconds since message created)
chat editing grace period max diff for low trust levels (number of characters changed)
chat editing grace period max diff for high trust levels (number of characters changed)
This PR addresses the push to unify the icon representing AI throughout Discourse, by using the discourse-sparkles icon.
The icon is being moved to core to make changes with dependencies included in core that were using the "magic" icon instead.
In 2 places "magic" -> "discourse-sparkles,
1. topic summaries
2. (unreleased) chat summaries example
These helpers are never used in raw-hbs. Exporting default functions is enough for them to be picked up by the ember resolver, and also makes it possible to use them in gjs files.
This commit adds a new admin UI under the route `/admin-revamp`, which is
only accessible if the user is in a group defined by the new `enable_experimental_admin_ui_groups` site setting. It
also adds a special `admin` sidebar panel that is shown instead of the `main`
forum one when the admin is in this area.
![image](https://github.com/discourse/discourse/assets/920448/fa0f25e1-e178-4d94-aa5f-472fd3efd787)
We also add an "Admin Revamp" sidebar link to the community section, which
will only appear if the user is in the setting group:
![image](https://github.com/discourse/discourse/assets/920448/ec05ca8b-5a54-442b-ba89-6af35695c104)
Within this there are subroutes defined like `/admin-revamp/config/:area`,
these areas could contain any UI imaginable, this is just laying down an
initial idea of the structure and how the sidebar will work. Sidebar links are
currently hardcoded.
Some other changes:
* Changed the `main` and `chat` panels sidebar panel keys to use exported const values for reuse
* Allowed custom sidebar sections to hide their headers with the `hideSectionHeader` option
* Add a `groupSettingArray` setting on `this.siteSettings` in JS, which accepts a group site setting name
and splits it by `|` then converts the items in the array to integers, similar to the `_map` magic for ruby
group site settings
* Adds a `hidden` option for sidebar panels which prevents them from showing in separated mode and prevents
the switch button from being shown
---------
Co-authored-by: Krzysztof Kotlarek <kotlarek.krzysztof@gmail.com>
As of #23867 this is now a real package, so updating the imports to
use the real package name, rather than relying on the alias. The
name change in the package name is because `I18n` is not a valid
name as NPM packages must be all lowercase.
This commit also introduces an eslint rule to prevent importing from
the old I18n path.
For themes/plugins, the old 'i18n' name remains functional.
Users can decide to hide their profile and presence. It seems more correct to also not return the status in this case.
Co-authored-by: Penar Musaraj <pmusaraj@gmail.com>
* Revert "UX: place (edited) on same line (#23866)"
This reverts commit c1017a479b.
* Revert "UX: prevent (edited) and following from being copied (#23882)"
This reverts commit 563bff509a.
We now create threads on reply so the refresh check is not really necessary as there's nothing special about it anymore. We don't refresh every pages in other tests to check they still work.
Hopefully these changes will prevent few flakeys too.
The setting will change from "%{count} days" to "Chat settings have been set to retain channel messages for %{count} day."
This commit also:
- migrates `chat-retention-reminder` to gjs
- adds a "type" property to `chat-retention-reminder-text` to allow use a long or short text depending on where it's used.
When visiting a channel which has unread threads, we will now open the threads list panel.
Note that:
mobile
linking to message
linking to a thread
Won't open the threads list.
Controller queryParam configuration should be wrapped in an array. Omitting the array wrapper seems to work under Ember 3.28, but causes an error under Ember 5.
For the admin plugin list we want to be able to link to
a meta topic for plugins, but we have no standard way to
do this at the moment. This adds support for meta_topic_id
alongside other plugin metadata like authors, URL etc,
that gets built into a Meta topic URL in the serializer.
It was slightly surprising to have a user card show when click on a thread item list.
More over this commit does:
- moves chat/user-avatar to chat-user-avatar and converts it to gjs
- moves chat/thread/participants to chat-thread-participants
- rewrite the `toggleCheckIfPossible` modifier to only be applied when selecting messages, it prevents the click event to collide with the click of avatars in regular messages
Consolidated chat notifications went live for a short amount of time then reverted the commit due to UX concerns.
The result of this is that there are a few users affected that will have notifications in a blank state.
As a workaround this PR will hide those notifications until the feature is ready to merge again.
This PR is a first step towards private groups. It redesigns settings/members area of a channel and also drops the "about" page which is now mixed into settings.
This commit is also:
- introducing chat-form, a small DSL to create forms, ideally I would want something in core for this
- introducing a DToggleSwitch page object component to simplify testing toggles
- migrating various components to gjs
Faker can generate test containing `...` which will get converted to `…` by `PrettyText`, it means that we can't use the input to check the output. This commit simply normalise the generated text to ensure this part of the input is not modified.
I don't have a repro of this ATM, but I suspect that ensuring the panel has been opened before moving to next tests could make this test more resilient.
At the moment writing a mention similar to `@bob...hi` would have resulted in chat trying to find a user named `bob...hi` which would fail.
This was due to the `replacements` rule not being present in the rules used to cook chat messages.
We are still missing few default rules like: normalize, smartquotes, text_join, ... which don't seem to be necessary but could be added if we found a reason for. More info at: e476f78bc3/lib/parser_core.js
Workaround for an issue we are experiencing on thread index frontend where thread loads participants correctly (up to 10), then refreshes the threads and then limits to 3 participants.
There is an issue with storing threads for the main channel view and the thread list in the same store so handling the max participants on the frontend is a workaround until channel.threadsManager is updated.
I've adjusted the tests to handle the additional data being returned from ThreadParticipantQuery.
Why this change?
Back in May 17 2023 along with the release of Discourse 3.1, we announced
on meta that the legacy hamburger dropdown navigation menu is
deprecated and will be dropped in Discourse 3.2. This is the link to the announcement
on meta: https://meta.discourse.org/t/removing-the-legacy-hamburger-navigation-menu-option/265274
## What does this change do?
This change removes the `legacy` option from the `navigation_menu` site
setting and migrates existing sites on the `legacy` option to the
`header dropdown` option.
All references to the `legacy` option in code and tests have been
removed as well.
* UX: fix padding on active touch
* UX: thread list header bigger + alignment
* UX: thread list alignment desktop
* UX: visual corrections to alignment
* UX: mobile alignment and padding
UX changes to thread item:
- drop "last reply" timestamp copy
- drop last reply excerpt
- show up 9+OP members
Co-authored-by: David Battersby <info@davidbattersby.com>
We noticed some of these tests were flaky, sometimes they fail on assertions
related to testing tooltips. Tooltips are generally hard to test, and it's not
necessary to test tooltips in every test case. This PR isolates tooltip testing
in a dedicated test case.
Note we already did the same thing for another spec in a9dfda2 and that
seems to worked well.
Tooltips are generally hard to test, and we noticed that they make tests flaky.
We may not test tooltips in these acceptance tests, since they are already
tested in components/chat-channel-test.js. This PR deletes tooltip-related
assertions and unskips tests that became flaky after adding them.
Partial revert of 97a812f022.
Calling hide_plugin also hides the Chat tab in the plugins
section of admin, which is a way plugins can add an advanced
UI (in the case of chat Export and Webhooks).
Until we decide what to do in this case, it's better to revert,
since all this will do is make the discourse-chat plugin show
up again in the plugin list and restore the missing tab.
This commit brings two fixes.
- increase the delay to trigger the action menu
- check of user activation before using vibrate:
https://developer.mozilla.org/en-US/docs/Glossary/Sticky_activationhttps://developer.mozilla.org/en-US/docs/Web/Security/User_activationhttps://developer.mozilla.org/en-US/docs/Web/API/UserActivation/hasBeenActive
> Sticky activation is a window state that indicates a user has pressed a button, moved a mouse, used a menu, or performed some other user interaction. It is not reset after it has been set initially (unlike transient activation).
> APIs that require sticky activation (not exhaustive):
> - Navigator.vibrate()
> - VirtualKeyboard.show()
> - Autoplay of Media and Web Audio APIs (in particular for AudioContexts).
Before this fix, we could end up with this error in the console in tests:
> Blocked call to navigator.vibrate because user hasn't tapped on the frame or any embedded
<!-- NOTE: All pull requests should have tests (rspec in Ruby, qunit in JavaScript). If your code does not include test coverage, please include an explanation of why it was omitted. -->
- Allows to copy quotes from mobile
- Allows to copy text of a message from mobile
- Allows to select messages by clicking on it when selection has started
Note this commit is also now using toasts to show a confirmation of copy, and refactors system specs helpers concerning secondary actions.
<!-- NOTE: All pull requests should have tests (rspec in Ruby, qunit in JavaScript). If your code does not include test coverage, please include an explanation of why it was omitted. -->
1. actually call `popupAjaxError`, thanks :P
2. don't close a modal on error
3. use `extractError()` instead of manually joining error messages
4. …or passing just the error object to `this.flash`
As per 92839dc722 lastMessage won't be null when a message is deleted. This would cause sorting issues when messages from a direct message channels are deleted as we would try to sort on last message and thought it would exist when actually it would be a null message.
I might rethink this design to not return any last_message in this case soon as checking on ID feels dirty and prone to error, so only fixing the issue for now.
This commit is also using `@cached` to avoid replaying the sort logic on each access.
This PR introduces three new concepts to Discourse codebase through an addon called "FloatKit":
- menu
- tooltip
- toast
## Tooltips
### Component
Simple cases can be express with an API similar to DButton:
```hbs
<DTooltip
@Label={{i18n "foo.bar"}}
@ICON="check"
@content="Something"
/>
```
More complex cases can use blocks:
```hbs
<DTooltip>
<:trigger>
{{d-icon "check"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
Something
</:content>
</DTooltip>
```
### Service
You can manually show a tooltip using the `tooltip` service:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
tooltipInstance.close();
tooltipInstance.destroy();
// you can also just close any open tooltip through the service
this.tooltip.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const tooltipInstance = this.tooltip.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
tooltipInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Menus
Menus are very similar to tooltips and provide the same kind of APIs:
### Component
```hbs
<DMenu @ICON="plus" @Label={{i18n "foo.bar"}}>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</DMenu>
```
They also support blocks:
```hbs
<DMenu>
<:trigger>
{{d-icon "plus"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</:content>
</DMenu>
```
### Service
You can manually show a menu using the `menu` service:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
menuInstance.close();
menuInstance.destroy();
// you can also just close any open tooltip through the service
this.menu.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const menuInstance = this.menu.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
menuInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Toasts
Interacting with toasts is made only through the `toasts` service.
A default component is provided (DDefaultToast) and can be used through dedicated service methods:
- this.toasts.success({ ... });
- this.toasts.warning({ ... });
- this.toasts.info({ ... });
- this.toasts.error({ ... });
- this.toasts.default({ ... });
```javascript
this.toasts.success({
data: {
title: "Foo",
message: "Bar",
actions: [
{
label: "Ok",
class: "btn-primary",
action: (componentArgs) => {
// eslint-disable-next-line no-alert
alert("Closing toast:" + componentArgs.data.title);
componentArgs.close();
},
}
]
},
});
```
You can also provide your own component:
```javascript
this.toasts.show(MyComponent, {
autoClose: false,
class: "foo",
data: { baz: 1 },
})
```
Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
Why this change?
Before this change, we were doing a partial match when checking for
existence. This is a source of flaky tests because a chat message with
text `this is a message` will match any substring like `message` or `a`.
What does this change do?
This change removes the partial match and instead opts for the
`exact_text` option instead.
This would cause an error when deleting the original message of a thread, due to the non existing `last_message`. This fix is implemented using the null pattern.
Note this commit is also using this opportunity to unify naming of null objects, `Chat::DeletedUser` becomes `Chat::NullUser`, it feels better to have a name describing what is the object, instead of a name describing why this object has to be used, which can change depending on cases.
While very fast and powerful staged threads forces a lot of gymnastic and edge cases. This patch adds a new service `Chat::CreateThread` and uses it to create a thread unconditionally when a user replies to a message in a threading enabled channel. If the user actually doesn’t send a message we will have a thread with no messages which has no important impact and could even be periodically cleaned if necessary.
Note that this commit also moves message actions to .gjs as it was the original goal of this PR to correctly check for staged thread to show the menu or not.
This feature adds notifications for chat messages that are sent within personal chats (1:1 and personal group chats).
To prevent notification spam we make use of consolidated notifications to combine updated message information in a meaningful way that allows the receiver to quickly jump into the chat to see what they missed.
This update respects muted channels, muted and blocked users. It will only create a new notification when the user has not muted the channel and the notified user is not muting or ignoring the message sender.
Why this change?
We have been bitten by bugs where tests are not catching missing
interpolate argument in our client side code because the JavaScript
tests are also using `I18n.translate` to assert that the right message
is shown. Before this change, `I18n.interpolate` will just replace the
missing interpolation argument in the final translation with some
placeholder. As a result, we ended up comparing a broken translation
with another broken translation in the test environment.
Why does this change do?
This change introduces the `I18n.testing` property which when set to
`true` will cause `I18n.translate` to throw an error when an interpolate
argument is missing. With this commit, we also set `I18n.testing = true`
when running qunit acceptance test.
Second iteration of https://github.com/discourse/discourse/pull/23312 with a fix for embroider not resolving an export file using .gjs extension.
---
This PR introduces three new concepts to Discourse codebase through an addon called "FloatKit":
- menu
- tooltip
- toast
## Tooltips
### Component
Simple cases can be express with an API similar to DButton:
```hbs
<DTooltip
@label={{i18n "foo.bar"}}
@icon="check"
@content="Something"
/>
```
More complex cases can use blocks:
```hbs
<DTooltip>
<:trigger>
{{d-icon "check"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
Something
</:content>
</DTooltip>
```
### Service
You can manually show a tooltip using the `tooltip` service:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
tooltipInstance.close();
tooltipInstance.destroy();
// you can also just close any open tooltip through the service
this.tooltip.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const tooltipInstance = this.tooltip.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
tooltipInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Menus
Menus are very similar to tooltips and provide the same kind of APIs:
### Component
```hbs
<DMenu @icon="plus" @label={{i18n "foo.bar"}}>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</DMenu>
```
They also support blocks:
```hbs
<DMenu>
<:trigger>
{{d-icon "plus"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</:content>
</DMenu>
```
### Service
You can manually show a menu using the `menu` service:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
options
)
// and later manual close or destroy it
menuInstance.close();
menuInstance.destroy();
// you can also just close any open tooltip through the service
this.menu.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const menuInstance = this.menu.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
menuInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
## Toasts
Interacting with toasts is made only through the `toasts` service.
A default component is provided (DDefaultToast) and can be used through dedicated service methods:
- this.toasts.success({ ... });
- this.toasts.warning({ ... });
- this.toasts.info({ ... });
- this.toasts.error({ ... });
- this.toasts.default({ ... });
```javascript
this.toasts.success({
data: {
title: "Foo",
message: "Bar",
actions: [
{
label: "Ok",
class: "btn-primary",
action: (componentArgs) => {
// eslint-disable-next-line no-alert
alert("Closing toast:" + componentArgs.data.title);
componentArgs.close();
},
}
]
},
});
```
You can also provide your own component:
```javascript
this.toasts.show(MyComponent, {
autoClose: false,
class: "foo",
data: { baz: 1 },
})
```
Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
This PR introduces three new UI elements to Discourse codebase through an addon called "FloatKit":
- menu
- tooltip
- toast
Simple cases can be express with an API similar to DButton:
```hbs
<DTooltip
@label={{i18n "foo.bar"}}
@icon="check"
@content="Something"
/>
```
More complex cases can use blocks:
```hbs
<DTooltip>
<:trigger>
{{d-icon "check"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
Something
</:content>
</DTooltip>
```
You can manually show a tooltip using the `tooltip` service:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
options
)
// and later manually close or destroy it
tooltipInstance.close();
tooltipInstance.destroy();
// you can also just close any open tooltip through the service
this.tooltip.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const tooltipInstance = this.tooltip.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
tooltipInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const tooltipInstance = await this.tooltip.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
Menus are very similar to tooltips and provide the same kind of APIs:
```hbs
<DMenu @icon="plus" @label={{i18n "foo.bar"}}>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</DMenu>
```
They also support blocks:
```hbs
<DMenu>
<:trigger>
{{d-icon "plus"}}
<span>{{i18n "foo.bar"}}</span>
</:trigger>
<:content>
<ul>
<li>Foo</li>
<li>Bat</li>
<li>Baz</li>
</ul>
</:content>
</DMenu>
```
You can manually show a menu using the `menu` service:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
options
)
// and later manually close or destroy it
menuInstance.close();
menuInstance.destroy();
// you can also just close any open tooltip through the service
this.menu.close();
```
The service also allows you to register event listeners on a trigger, it removes the need for you to manage open/close of a tooltip started through the service:
```javascript
const menuInstance = this.menu.register(
document.querySelector(".my-span"),
options
)
// when done you can destroy the instance to remove the listeners
menuInstance.destroy();
```
Note that the service also allows you to use a custom component as content which will receive `@data` and `@close` as args:
```javascript
const menuInstance = await this.menu.show(
document.querySelector(".my-span"),
{
component: MyComponent,
data: { foo: 1 }
}
)
```
Interacting with toasts is made only through the `toasts` service.
A default component is provided (DDefaultToast) and can be used through dedicated service methods:
- this.toasts.success({ ... });
- this.toasts.warning({ ... });
- this.toasts.info({ ... });
- this.toasts.error({ ... });
- this.toasts.default({ ... });
```javascript
this.toasts.success({
data: {
title: "Foo",
message: "Bar",
actions: [
{
label: "Ok",
class: "btn-primary",
action: (componentArgs) => {
// eslint-disable-next-line no-alert
alert("Closing toast:" + componentArgs.data.title);
componentArgs.close();
},
}
]
},
});
```
You can also provide your own component:
```javascript
this.toasts.show(MyComponent, {
autoClose: false,
class: "foo",
data: { baz: 1 },
})
```
Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
Co-authored-by: Isaac Janzen <50783505+janzenisaac@users.noreply.github.com>
Co-authored-by: David Taylor <david@taylorhq.com>
Co-authored-by: Jarek Radosz <jradosz@gmail.com>
- do not prevent the event (it was a violation anyways as the touchstart is passive)
- waits for at least 25px horizontal move before showing the remove action, it avoids showing the remove while scrolling and moving a little bit horizontally
On mobile swiping a channel row will now show a "Remove" option. Holding this to the end will now remove this row from your list of followed direct message channels.
Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
Introduce max length on text columns for description and slug fields within chat.
At a later date we will probably want to convert these text columns to string/varchar through a migration, but for now this change introduces a limit within the active record model.