User options were serialized at the root level of CurrentUserSerializer, but UserSerializer has a user_option field. This inconsistency caused issues in the past because user_option fields had to be duplicated on the frontend.
493 lines
14 KiB
JavaScript
493 lines
14 KiB
JavaScript
import { htmlSafe } from "@ember/template";
|
|
import slugifyChannel from "discourse/plugins/chat/discourse/lib/slugify-channel";
|
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
|
import I18n from "I18n";
|
|
import { bind } from "discourse-common/utils/decorators";
|
|
import { tracked } from "@glimmer/tracking";
|
|
import { avatarUrl, escapeExpression } from "discourse/lib/utilities";
|
|
import { dasherize } from "@ember/string";
|
|
import { emojiUnescape } from "discourse/lib/text";
|
|
import { decorateUsername } from "discourse/helpers/decorate-username-selector";
|
|
import { until } from "discourse/lib/formatter";
|
|
import { inject as service } from "@ember/service";
|
|
import { computed } from "@ember/object";
|
|
|
|
export default {
|
|
name: "chat-sidebar",
|
|
initialize(container) {
|
|
this.chatService = container.lookup("service:chat");
|
|
|
|
if (!this.chatService.userCanChat) {
|
|
return;
|
|
}
|
|
|
|
withPluginApi("1.3.0", (api) => {
|
|
api.addSidebarSection(
|
|
(BaseCustomSidebarSection, BaseCustomSidebarSectionLink) => {
|
|
const SidebarChatChannelsSectionLink = class extends BaseCustomSidebarSectionLink {
|
|
@tracked chatChannelTrackingState =
|
|
this.chatService.currentUser.chat_channel_tracking_state[
|
|
this.channel.id
|
|
];
|
|
|
|
constructor({ channel, chatService }) {
|
|
super(...arguments);
|
|
this.channel = channel;
|
|
this.chatService = chatService;
|
|
}
|
|
|
|
@bind
|
|
willDestroy() {
|
|
this.chatService.appEvents.off(
|
|
"chat:user-tracking-state-changed",
|
|
this._refreshTrackingState
|
|
);
|
|
}
|
|
|
|
@bind
|
|
didInsert() {
|
|
this.chatService.appEvents.on(
|
|
"chat:user-tracking-state-changed",
|
|
this._refreshTrackingState
|
|
);
|
|
}
|
|
|
|
@bind
|
|
_refreshTrackingState() {
|
|
this.chatChannelTrackingState =
|
|
this.chatService.currentUser.chat_channel_tracking_state[
|
|
this.channel.id
|
|
];
|
|
}
|
|
|
|
get name() {
|
|
return dasherize(slugifyChannel(this.channel));
|
|
}
|
|
|
|
@computed("chatService.activeChannel")
|
|
get classNames() {
|
|
const classes = [];
|
|
|
|
if (this.channel.current_user_membership.muted) {
|
|
classes.push("sidebar-section-link--muted");
|
|
}
|
|
|
|
if (this.channel.id === this.chatService.activeChannel?.id) {
|
|
classes.push("sidebar-section-link--active");
|
|
}
|
|
|
|
return classes.join(" ");
|
|
}
|
|
|
|
get route() {
|
|
return "chat.channel";
|
|
}
|
|
|
|
get models() {
|
|
return [this.channel.id, slugifyChannel(this.channel)];
|
|
}
|
|
|
|
get text() {
|
|
return htmlSafe(emojiUnescape(this.channel.escapedTitle));
|
|
}
|
|
|
|
get prefixType() {
|
|
return "icon";
|
|
}
|
|
|
|
get prefixValue() {
|
|
return "hashtag";
|
|
}
|
|
|
|
get prefixColor() {
|
|
return this.channel.chatable.color;
|
|
}
|
|
|
|
get title() {
|
|
return this.channel.escapedDescription
|
|
? htmlSafe(this.channel.escapedDescription)
|
|
: `${this.channel.escapedTitle} ${I18n.t("chat.title")}`;
|
|
}
|
|
|
|
get prefixBadge() {
|
|
return this.channel.chatable.read_restricted ? "lock" : "";
|
|
}
|
|
|
|
get suffixType() {
|
|
return "icon";
|
|
}
|
|
|
|
get suffixValue() {
|
|
return this.chatChannelTrackingState?.unread_count > 0
|
|
? "circle"
|
|
: "";
|
|
}
|
|
|
|
get suffixCSSClass() {
|
|
return this.chatChannelTrackingState?.unread_mentions > 0
|
|
? "urgent"
|
|
: "unread";
|
|
}
|
|
};
|
|
|
|
const SidebarChatChannelsSection = class extends BaseCustomSidebarSection {
|
|
@tracked sectionLinks = [];
|
|
|
|
@tracked sectionIndicator =
|
|
this.chatService.publicChannels &&
|
|
this.chatService.publicChannels[0].current_user_membership
|
|
.unread_count;
|
|
|
|
@tracked currentUserCanJoinPublicChannels =
|
|
this.sidebar.currentUser &&
|
|
(this.sidebar.currentUser.staff ||
|
|
this.sidebar.currentUser.has_joinable_public_channels);
|
|
|
|
constructor() {
|
|
super(...arguments);
|
|
|
|
if (container.isDestroyed) {
|
|
return;
|
|
}
|
|
this.chatService = container.lookup("service:chat");
|
|
this.router = container.lookup("service:router");
|
|
this.appEvents = container.lookup("service:app-events");
|
|
this.appEvents.on("chat:refresh-channels", this._refreshChannels);
|
|
this._refreshChannels();
|
|
}
|
|
|
|
@bind
|
|
willDestroy() {
|
|
if (!this.appEvents) {
|
|
return;
|
|
}
|
|
this.appEvents.off(
|
|
"chat:refresh-channels",
|
|
this._refreshChannels
|
|
);
|
|
}
|
|
|
|
@bind
|
|
_refreshChannels() {
|
|
const newSectionLinks = [];
|
|
this.chatService.getChannels().then((channels) => {
|
|
channels.publicChannels.forEach((channel) => {
|
|
newSectionLinks.push(
|
|
new SidebarChatChannelsSectionLink({
|
|
channel,
|
|
chatService: this.chatService,
|
|
})
|
|
);
|
|
});
|
|
this.sectionLinks = newSectionLinks;
|
|
});
|
|
}
|
|
|
|
get name() {
|
|
return "chat-channels";
|
|
}
|
|
|
|
get title() {
|
|
return I18n.t("chat.chat_channels");
|
|
}
|
|
|
|
get text() {
|
|
return I18n.t("chat.chat_channels");
|
|
}
|
|
|
|
get actions() {
|
|
return [
|
|
{
|
|
id: "browseChannels",
|
|
title: I18n.t("chat.channels_list_popup.browse"),
|
|
action: () => this.router.transitionTo("chat.browse.open"),
|
|
},
|
|
];
|
|
}
|
|
|
|
get actionsIcon() {
|
|
return "pencil-alt";
|
|
}
|
|
|
|
get links() {
|
|
return this.sectionLinks;
|
|
}
|
|
|
|
get displaySection() {
|
|
return (
|
|
this.sectionLinks.length > 0 ||
|
|
this.currentUserCanJoinPublicChannels
|
|
);
|
|
}
|
|
};
|
|
|
|
return SidebarChatChannelsSection;
|
|
}
|
|
);
|
|
|
|
api.addSidebarSection(
|
|
(BaseCustomSidebarSection, BaseCustomSidebarSectionLink) => {
|
|
const SidebarChatDirectMessagesSectionLink = class extends BaseCustomSidebarSectionLink {
|
|
@tracked chatChannelTrackingState =
|
|
this.chatService.currentUser.chat_channel_tracking_state[
|
|
this.channel.id
|
|
];
|
|
|
|
constructor({ channel, chatService }) {
|
|
super(...arguments);
|
|
this.channel = channel;
|
|
this.chatService = chatService;
|
|
|
|
if (this.oneOnOneMessage) {
|
|
this.channel.chatable.users[0].trackStatus();
|
|
}
|
|
}
|
|
|
|
@bind
|
|
willDestroy() {
|
|
if (this.oneOnOneMessage) {
|
|
this.channel.chatable.users[0].stopTrackingStatus();
|
|
}
|
|
}
|
|
|
|
get name() {
|
|
return slugifyChannel(this.channel);
|
|
}
|
|
|
|
@computed("chatService.activeChannel")
|
|
get classNames() {
|
|
const classes = [];
|
|
|
|
if (this.channel.current_user_membership.muted) {
|
|
classes.push("sidebar-section-link--muted");
|
|
}
|
|
|
|
if (this.channel.id === this.chatService.activeChannel?.id) {
|
|
classes.push("sidebar-section-link--active");
|
|
}
|
|
|
|
return classes.join(" ");
|
|
}
|
|
|
|
get route() {
|
|
return "chat.channel";
|
|
}
|
|
|
|
get models() {
|
|
return [this.channel.id, slugifyChannel(this.channel)];
|
|
}
|
|
|
|
get title() {
|
|
return I18n.t("chat.placeholder_others", {
|
|
messageRecipient: this.channel.escapedTitle,
|
|
});
|
|
}
|
|
|
|
get oneOnOneMessage() {
|
|
return this.channel.chatable.users.length === 1;
|
|
}
|
|
|
|
get text() {
|
|
const username = this.channel.escapedTitle.replaceAll("@", "");
|
|
if (this.oneOnOneMessage) {
|
|
const status = this.channel.chatable.users[0].get("status");
|
|
const statusHtml = status ? this._userStatusHtml(status) : "";
|
|
return htmlSafe(
|
|
`${escapeExpression(
|
|
username
|
|
)}${statusHtml} ${decorateUsername(
|
|
escapeExpression(username)
|
|
)}`
|
|
);
|
|
} else {
|
|
return username;
|
|
}
|
|
}
|
|
|
|
get prefixType() {
|
|
if (this.oneOnOneMessage) {
|
|
return "image";
|
|
} else {
|
|
return "text";
|
|
}
|
|
}
|
|
|
|
get prefixValue() {
|
|
if (this.channel.chatable.users.length === 1) {
|
|
return avatarUrl(
|
|
this.channel.chatable.users[0].avatar_template,
|
|
"tiny"
|
|
);
|
|
} else {
|
|
return this.channel.chatable.users.length;
|
|
}
|
|
}
|
|
|
|
get prefixCSSClass() {
|
|
const activeUsers = this.chatService.presenceChannel.users;
|
|
const user = this.channel.chatable.users[0];
|
|
if (
|
|
!!activeUsers?.findBy("id", user?.id) ||
|
|
!!activeUsers?.findBy("username", user?.username)
|
|
) {
|
|
return "active";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
get suffixType() {
|
|
return "icon";
|
|
}
|
|
|
|
get suffixValue() {
|
|
return this.chatChannelTrackingState?.unread_count > 0
|
|
? "circle"
|
|
: "";
|
|
}
|
|
|
|
get suffixCSSClass() {
|
|
return "urgent";
|
|
}
|
|
|
|
get hoverType() {
|
|
return "icon";
|
|
}
|
|
|
|
get hoverValue() {
|
|
return "times";
|
|
}
|
|
|
|
get hoverAction() {
|
|
return (event) => {
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
this.chatService.unfollowChannel(this.channel);
|
|
};
|
|
}
|
|
|
|
get hoverTitle() {
|
|
return I18n.t("chat.direct_messages.leave");
|
|
}
|
|
|
|
_userStatusHtml(status) {
|
|
const emoji = escapeExpression(`:${status.emoji}:`);
|
|
const title = this._userStatusTitle(status);
|
|
return `<span class="user-status">${emojiUnescape(emoji, {
|
|
title,
|
|
})}</span>`;
|
|
}
|
|
|
|
_userStatusTitle(status) {
|
|
let title = `${escapeExpression(status.description)}`;
|
|
|
|
if (status.ends_at) {
|
|
const untilFormatted = until(
|
|
status.ends_at,
|
|
this.chatService.currentUser.user_option.timezone,
|
|
this.chatService.currentUser.locale
|
|
);
|
|
title += ` ${untilFormatted}`;
|
|
}
|
|
|
|
return title;
|
|
}
|
|
};
|
|
|
|
const SidebarChatDirectMessagesSection = class extends BaseCustomSidebarSection {
|
|
@service site;
|
|
@service router;
|
|
@tracked sectionLinks = [];
|
|
@tracked userCanDirectMessage =
|
|
this.chatService.userCanDirectMessage;
|
|
|
|
constructor() {
|
|
super(...arguments);
|
|
|
|
if (container.isDestroyed) {
|
|
return;
|
|
}
|
|
this.chatService = container.lookup("service:chat");
|
|
this.chatService.appEvents.on(
|
|
"chat:user-tracking-state-changed",
|
|
this._refreshDirectMessageChannels
|
|
);
|
|
this._refreshDirectMessageChannels();
|
|
}
|
|
|
|
@bind
|
|
willDestroy() {
|
|
if (container.isDestroyed) {
|
|
return;
|
|
}
|
|
this.chatService.appEvents.off(
|
|
"chat:user-tracking-state-changed",
|
|
this._refreshDirectMessageChannels
|
|
);
|
|
}
|
|
|
|
@bind
|
|
_refreshDirectMessageChannels() {
|
|
const newSectionLinks = [];
|
|
this.chatService.getChannels().then((channels) => {
|
|
this.chatService
|
|
.truncateDirectMessageChannels(channels.directMessageChannels)
|
|
.forEach((channel) => {
|
|
newSectionLinks.push(
|
|
new SidebarChatDirectMessagesSectionLink({
|
|
channel,
|
|
chatService: this.chatService,
|
|
})
|
|
);
|
|
});
|
|
this.sectionLinks = newSectionLinks;
|
|
});
|
|
}
|
|
|
|
get name() {
|
|
return "chat-dms";
|
|
}
|
|
|
|
get title() {
|
|
return I18n.t("chat.direct_messages.title");
|
|
}
|
|
|
|
get text() {
|
|
return I18n.t("chat.direct_messages.title");
|
|
}
|
|
|
|
get actions() {
|
|
if (!this.userCanDirectMessage) {
|
|
return [];
|
|
}
|
|
|
|
return [
|
|
{
|
|
id: "startDm",
|
|
title: I18n.t("chat.direct_messages.new"),
|
|
action: () => {
|
|
this.router.transitionTo("chat.draft-channel");
|
|
},
|
|
},
|
|
];
|
|
}
|
|
|
|
get actionsIcon() {
|
|
return "plus";
|
|
}
|
|
|
|
get links() {
|
|
return this.sectionLinks;
|
|
}
|
|
|
|
get displaySection() {
|
|
return this.sectionLinks.length > 0 || this.userCanDirectMessage;
|
|
}
|
|
};
|
|
|
|
return SidebarChatDirectMessagesSection;
|
|
}
|
|
);
|
|
});
|
|
},
|
|
};
|