This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.
osr-discourse-src/plugins/chat/assets/javascripts/discourse/components/chat-channel-selector-modal-inner.js
Joffrey JAFFEUX 96e2d92129
FIX: at this point in time component can be destroyed (#19677)
Especially possible in tests
2023-01-02 19:17:10 +01:00

232 lines
6.7 KiB
JavaScript

import Component from "@ember/component";
import { action } from "@ember/object";
import ChatChannel from "discourse/plugins/chat/discourse/models/chat-channel";
import { ajax } from "discourse/lib/ajax";
import { bind } from "discourse-common/utils/decorators";
import { schedule } from "@ember/runloop";
import { inject as service } from "@ember/service";
import { popupAjaxError } from "discourse/lib/ajax-error";
import discourseDebounce from "discourse-common/lib/debounce";
import { INPUT_DELAY } from "discourse-common/config/environment";
import { isPresent } from "@ember/utils";
export default Component.extend({
chat: service(),
tagName: "",
filter: "",
channels: null,
searchIndex: 0,
loading: false,
chatChannelsManager: service(),
didInsertElement() {
this._super(...arguments);
this.appEvents.on("chat-channel-selector-modal:close", this.close);
document.addEventListener("keyup", this.onKeyUp);
document
.getElementById("chat-channel-selector-modal-inner")
?.addEventListener("mouseover", this.mouseover);
document.getElementById("chat-channel-selector-input")?.focus();
this.getInitialChannels();
},
willDestroyElement() {
this._super(...arguments);
this.appEvents.off("chat-channel-selector-modal:close", this.close);
document.removeEventListener("keyup", this.onKeyUp);
document
.getElementById("chat-channel-selector-modal-inner")
?.removeEventListener("mouseover", this.mouseover);
},
@bind
mouseover(e) {
if (e.target.classList.contains("chat-channel-selection-row")) {
let channel;
const id = parseInt(e.target.dataset.id, 10);
if (e.target.classList.contains("channel-row")) {
channel = this.channels.findBy("id", id);
} else {
channel = this.channels.find((c) => c.user && c.id === id);
}
channel?.set("focused", true);
this.channels.forEach((c) => {
if (c !== channel) {
c.set("focused", false);
}
});
}
},
@bind
onKeyUp(e) {
if (e.key === "Enter") {
let focusedChannel = this.channels.find((c) => c.focused);
this.switchChannel(focusedChannel);
e.preventDefault();
} else if (e.key === "ArrowDown") {
this.arrowNavigateChannels("down");
e.preventDefault();
} else if (e.key === "ArrowUp") {
this.arrowNavigateChannels("up");
e.preventDefault();
}
},
arrowNavigateChannels(direction) {
const indexOfFocused = this.channels.findIndex((c) => c.focused);
if (indexOfFocused > -1) {
const nextIndex = direction === "down" ? 1 : -1;
const nextChannel = this.channels[indexOfFocused + nextIndex];
if (nextChannel) {
this.channels[indexOfFocused].set("focused", false);
nextChannel.set("focused", true);
}
} else {
this.channels[0].set("focused", true);
}
schedule("afterRender", () => {
let focusedChannel = document.querySelector(
"#chat-channel-selector-modal-inner .chat-channel-selection-row.focused"
);
focusedChannel?.scrollIntoView({ block: "nearest", inline: "start" });
});
},
@action
switchChannel(channel) {
if (channel.user) {
return this.fetchOrCreateChannelForUser(channel).then((response) => {
const newChannel = this.chatChannelsManager.store(response.channel);
return this.chatChannelsManager.follow(newChannel).then((c) => {
this.chat.openChannel(c);
this.close();
});
});
} else {
return this.chatChannelsManager.follow(channel).then((c) => {
this.chat.openChannel(c);
this.close();
});
}
},
@action
search(value) {
if (isPresent(value?.trim())) {
discourseDebounce(
this,
this.fetchChannelsFromServer,
value?.trim(),
INPUT_DELAY
);
} else {
discourseDebounce(this, this.getInitialChannels, INPUT_DELAY);
}
},
@action
fetchChannelsFromServer(filter) {
if (this.isDestroyed || this.isDestroying) {
return;
}
this.setProperties({
loading: true,
searchIndex: this.searchIndex + 1,
});
const thisSearchIndex = this.searchIndex;
ajax("/chat/api/chatables", { data: { filter } })
.then((searchModel) => {
if (this.searchIndex === thisSearchIndex) {
this.set("searchModel", searchModel);
const channels = searchModel.public_channels.concat(
searchModel.direct_message_channels,
searchModel.users
);
channels.forEach((c) => {
if (c.username) {
c.user = true; // This is used by the `chat-channel-selection-row` component
}
});
this.setProperties({
channels: channels.map((channel) => {
return channel.user
? ChatChannel.create(channel)
: this.chatChannelsManager.store(channel);
}),
loading: false,
});
this.focusFirstChannel(this.channels);
}
})
.catch(popupAjaxError);
},
@action
getInitialChannels() {
if (this.isDestroyed || this.isDestroying) {
return;
}
const channels = this.getChannelsWithFilter(this.filter);
this.set("channels", channels);
this.focusFirstChannel(channels);
},
@action
fetchOrCreateChannelForUser(user) {
return ajax("/chat/direct_messages/create.json", {
method: "POST",
data: { usernames: [user.username] },
}).catch(popupAjaxError);
},
focusFirstChannel(channels) {
channels.forEach((c) => c.set("focused", false));
channels[0]?.set("focused", true);
},
getChannelsWithFilter(filter, opts = { excludeActiveChannel: true }) {
let sortedChannels = this.chatChannelsManager.channels.sort((a, b) => {
return new Date(a.last_message_sent_at) > new Date(b.last_message_sent_at)
? -1
: 1;
});
const trimmedFilter = filter.trim();
const lowerCasedFilter = filter.toLowerCase();
return sortedChannels.filter((channel) => {
if (
opts.excludeActiveChannel &&
this.chat.activeChannel?.id === channel.id
) {
return false;
}
if (!trimmedFilter.length) {
return true;
}
if (channel.isDirectMessageChannel) {
let userFound = false;
channel.chatable.users.forEach((user) => {
if (
user.username.toLowerCase().includes(lowerCasedFilter) ||
user.name?.toLowerCase().includes(lowerCasedFilter)
) {
return (userFound = true);
}
});
return userFound;
} else {
return channel.title.toLowerCase().includes(lowerCasedFilter);
}
});
},
});