Files
discourse/plugins/chat/assets/javascripts/discourse/services/chat-channels-manager.js
Joffrey JAFFEUX d75d64bf16 FEATURE: new jump to channel menu (#22383)
This commit replaces two existing screens:
- draft
- channel selection modal

Main features compared to existing solutions
- features are now combined, meaning you can for example create multi users DM
- it will show users with chat disabled
- it shows unread state
- hopefully a better look/feel
- lots of small details and fixes...

Other noticeable fixes
- starting a DM with a user, even from the user card and clicking <kbd>Chat</kbd> will not show a green dot for the target user (or even the channel) until a message is actually sent
- it should almost never do a full page reload anymore

---------

Co-authored-by: Martin Brennan <mjrbrennan@gmail.com>
Co-authored-by: Jordan Vidrine <30537603+jordanvidrine@users.noreply.github.com>
Co-authored-by: chapoi <101828855+chapoi@users.noreply.github.com>
Co-authored-by: Mark VanLandingham <markvanlan@gmail.com>
2023-07-05 18:18:27 +02:00

169 lines
4.5 KiB
JavaScript

import Service, { inject as service } from "@ember/service";
import { debounce } from "discourse-common/utils/decorators";
import Promise from "rsvp";
import ChatChannel from "discourse/plugins/chat/discourse/models/chat-channel";
import { tracked } from "@glimmer/tracking";
import { TrackedObject } from "@ember-compat/tracked-built-ins";
import { popupAjaxError } from "discourse/lib/ajax-error";
const DIRECT_MESSAGE_CHANNELS_LIMIT = 20;
/*
The ChatChannelsManager service is responsible for managing the loaded chat channels.
It provides helpers to facilitate using and managing loaded channels instead of constantly
fetching them from the server.
*/
export default class ChatChannelsManager extends Service {
@service chatSubscriptionsManager;
@service chatApi;
@service currentUser;
@tracked _cached = new TrackedObject();
async find(id, options = { fetchIfNotFound: true }) {
const existingChannel = this.#findStale(id);
if (existingChannel) {
return Promise.resolve(existingChannel);
} else if (options.fetchIfNotFound) {
return this.#find(id);
} else {
return Promise.resolve();
}
}
get channels() {
return Object.values(this._cached);
}
store(channelObject, options = {}) {
let model;
if (!options.replace) {
model = this.#findStale(channelObject.id);
}
if (!model) {
if (channelObject instanceof ChatChannel) {
model = channelObject;
} else {
model = ChatChannel.create(channelObject);
}
this.#cache(model);
}
if (
channelObject.meta?.message_bus_last_ids?.channel_message_bus_last_id !==
undefined
) {
model.channelMessageBusLastId =
channelObject.meta.message_bus_last_ids.channel_message_bus_last_id;
}
return model;
}
async follow(model) {
this.chatSubscriptionsManager.startChannelSubscription(model);
if (!model.currentUserMembership.following) {
return this.chatApi.followChannel(model.id).then((membership) => {
model.currentUserMembership.following = membership.following;
model.currentUserMembership.muted = membership.muted;
model.currentUserMembership.desktopNotificationLevel =
membership.desktopNotificationLevel;
model.currentUserMembership.mobileNotificationLevel =
membership.mobileNotificationLevel;
return model;
});
} else {
return Promise.resolve(model);
}
}
async unfollow(model) {
this.chatSubscriptionsManager.stopChannelSubscription(model);
return this.chatApi.unfollowChannel(model.id).then((membership) => {
model.currentUserMembership = membership;
return model;
});
}
@debounce(300)
async markAllChannelsRead() {
// The user tracking state for each channel marked read will be propagated by MessageBus
return this.chatApi.markAllChannelsAsRead();
}
remove(model) {
this.chatSubscriptionsManager.stopChannelSubscription(model);
delete this._cached[model.id];
}
get allChannels() {
return [...this.publicMessageChannels, ...this.directMessageChannels].sort(
(a, b) => {
return b?.currentUserMembership?.lastViewedAt?.localeCompare?.(
a?.currentUserMembership?.lastViewedAt
);
}
);
}
get publicMessageChannels() {
return this.channels
.filter(
(channel) =>
channel.isCategoryChannel && channel.currentUserMembership.following
)
.sort((a, b) => a?.slug?.localeCompare?.(b?.slug));
}
get directMessageChannels() {
return this.#sortDirectMessageChannels(
this.channels.filter((channel) => {
const membership = channel.currentUserMembership;
return channel.isDirectMessageChannel && membership.following;
})
);
}
get truncatedDirectMessageChannels() {
return this.directMessageChannels.slice(0, DIRECT_MESSAGE_CHANNELS_LIMIT);
}
async #find(id) {
return this.chatApi
.channel(id)
.catch(popupAjaxError)
.then((result) => {
return this.store(result.channel);
});
}
#cache(channel) {
if (!channel) {
return;
}
this._cached[channel.id] = channel;
}
#findStale(id) {
return this._cached[id];
}
#sortDirectMessageChannels(channels) {
return channels.sort((a, b) => {
if (a.tracking.unreadCount === b.tracking.unreadCount) {
return new Date(a.lastMessageSentAt) > new Date(b.lastMessageSentAt)
? -1
: 1;
} else {
return a.tracking.unreadCount > b.tracking.unreadCount ? -1 : 1;
}
});
}
}