DEV: converts models to native classes (#21418)

- `ChatChannel`
- `UserChatChannelMembership`

Also creates a new `chat-direct-message` model used as the object for the`chatable` property of the `ChatChannel` when the `ChatChannel` is a direct message channel. When the chatable is a category a real `Category` object will now be returned.

Archive state of a `ChatChannel` is now hold in a `ChatChannelArchive` object.
This commit is contained in:
Joffrey JAFFEUX 2023-05-08 18:24:41 +02:00 committed by GitHub
parent afbeeea09f
commit 22521d3428
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 638 additions and 624 deletions

View File

@ -42,7 +42,7 @@ class Chat::Api::ChatablesController < Chat::ApiController
direct_message_channels =
if users.count > 0
# FIXME: investigate the cost of this query
Chat::Channel
Chat::DirectMessageChannel
.includes(chatable: :users)
.joins(direct_message: :direct_message_users)
.group(1)

View File

@ -2,6 +2,8 @@
module Chat
class DirectMessageSerializer < ApplicationSerializer
attributes :id
has_many :users, serializer: Chat::UserWithCustomFieldsAndStatusSerializer, embed: :objects
def users

View File

@ -40,7 +40,7 @@ export default Component.extend(ModalFunctionality, {
.createChannelArchive(this.chatChannel.id, this._data())
.then(() => {
this.flash(I18n.t("chat.channel_archive.process_started"), "success");
this.chatChannel.set("status", CHANNEL_STATUSES.archived);
this.chatChannel.status = CHANNEL_STATUSES.archived;
discourseLater(() => {
this.closeModal();

View File

@ -1,18 +1,25 @@
{{#if (and this.channel.archive_failed this.currentUser.admin)}}
<div class="alert alert-warn chat-channel-retry-archive">
<div class="chat-channel-archive-failed-message">
{{this.channelArchiveFailedMessage}}
</div>
{{#if this.shouldRender}}
{{#if @channel.archive.failed}}
<div
class={{concat-class
"alert alert-warn chat-channel-retry-archive"
@channel.status
}}
>
<div class="chat-channel-archive-failed-message">
{{this.channelArchiveFailedMessage}}
</div>
<div class="chat-channel-archive-failed-retry">
<DButton
@action={{action "retryArchive"}}
@label="chat.channel_archive.retry"
/>
<div class="chat-channel-archive-failed-retry">
<DButton
@action={{this.retryArchive}}
@label="chat.channel_archive.retry"
/>
</div>
</div>
</div>
{{else if (and this.channel.archive_completed this.currentUser.admin)}}
<div class="chat-channel-archive-status">
{{this.channelArchiveCompletedMessage}}
</div>
{{else if @channel.archive.completed}}
<div class={{concat-class "chat-channel-archive-status" @channel.status}}>
{{this.channelArchiveCompletedMessage}}
</div>
{{/if}}
{{/if}}

View File

@ -1,61 +1,53 @@
import Component from "@ember/component";
import Component from "@glimmer/component";
import { htmlSafe } from "@ember/template";
import I18n from "I18n";
import { popupAjaxError } from "discourse/lib/ajax-error";
import getURL from "discourse-common/lib/get-url";
import { action } from "@ember/object";
import discourseComputed from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
import { isPresent } from "@ember/utils";
export default Component.extend({
channel: null,
tagName: "",
chatApi: service(),
export default class ChatChannelArchiveStatus extends Component {
@service chatApi;
@service currentUser;
@discourseComputed(
"channel.status",
"channel.archived_messages",
"channel.total_messages",
"channel.archive_failed"
)
channelArchiveFailedMessage() {
const translationKey = !this.channel.archive_topic_id
get shouldRender() {
return this.currentUser.admin && isPresent(this.args.channel.archive);
}
get channelArchiveFailedMessage() {
const archive = this.args.channel.archive;
const translationKey = !archive.topicId
? "chat.channel_status.archive_failed_no_topic"
: "chat.channel_status.archive_failed";
return htmlSafe(
I18n.t(translationKey, {
completed: this.channel.archived_messages,
total: this.channel.total_messages,
topic_url: this._getTopicUrl(),
completed: archive.messages,
total: archive.totalMessages,
topic_url: this.#getTopicUrl(),
})
);
},
}
@discourseComputed(
"channel.status",
"channel.archived_messages",
"channel.total_messages",
"channel.archive_completed"
)
channelArchiveCompletedMessage() {
get channelArchiveCompletedMessage() {
return htmlSafe(
I18n.t("chat.channel_status.archive_completed", {
topic_url: this._getTopicUrl(),
topic_url: this.#getTopicUrl(),
})
);
},
}
@action
retryArchive() {
return this.chatApi
.createChannelArchive(this.channel.id)
.createChannelArchive(this.args.channel.id)
.catch(popupAjaxError);
},
}
_getTopicUrl() {
if (!this.channel.archive_topic_id) {
get #getTopicUrl() {
if (!this.args.channel.archive.topicId) {
return "";
}
return getURL(`/t/-/${this.channel.archive_topic_id}`);
},
});
return getURL(`/t/-/${this.args.channel.archive.topicId}`);
}
}

View File

@ -1,8 +1,8 @@
{{#unless this.site.mobileView}}
{{#if this.shouldRender}}
<DButton
@icon="times"
@action={{this.onLeaveChannel}}
@action={{@onLeaveChannel}}
@class="btn-flat chat-channel-leave-btn"
@title={{this.leaveChatTitleKey}}
/>
{{/unless}}
{{/if}}

View File

@ -1,25 +1,19 @@
import discourseComputed from "discourse-common/utils/decorators";
import Component from "@ember/component";
import { equal } from "@ember/object/computed";
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import { CHATABLE_TYPES } from "discourse/plugins/chat/discourse/models/chat-channel";
import { isPresent } from "@ember/utils";
export default class ChatChannelLeaveBtn extends Component {
@service chat;
@service site;
export default Component.extend({
tagName: "",
channel: null,
chat: service(),
get shouldRender() {
return !this.site.mobileView && isPresent(this.args.channel);
}
isDirectMessageRow: equal(
"channel.chatable_type",
CHATABLE_TYPES.directMessageChannel
),
@discourseComputed("isDirectMessageRow")
leaveChatTitleKey(isDirectMessageRow) {
if (isDirectMessageRow) {
get leaveChatTitleKey() {
if (this.args.channel.isDirectMessageChannel) {
return "chat.direct_messages.leave";
} else {
return "chat.leave";
}
},
});
}
}

View File

@ -19,7 +19,7 @@ export default class ChatChannelRow extends Component {
}
get channelHasUnread() {
return this.args.channel.currentUserMembership.unread_count > 0;
return this.args.channel.currentUserMembership.unreadCount > 0;
}
get #firstDirectMessageUser() {

View File

@ -5,9 +5,11 @@ import { action } from "@ember/object";
export default Component.extend({
tagName: "",
@discourseComputed("model", "model.focused")
rowClassNames(model, focused) {
return `chat-channel-selection-row ${focused ? "focused" : ""} ${
isFocused: false,
@discourseComputed("model", "isFocused")
rowClassNames(model, isFocused) {
return `chat-channel-selection-row ${isFocused ? "focused" : ""} ${
this.model.user ? "user-row" : "channel-row"
}`;
},

View File

@ -18,6 +18,7 @@
<ConditionalLoadingSpinner @condition={{this.loading}}>
{{#each this.channels as |channel|}}
<ChatChannelSelectionRow
@isFocused={{eq channel this.focusedRow}}
@model={{channel}}
@onClick={{this.switchChannel}}
/>

View File

@ -1,6 +1,5 @@
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";
@ -9,6 +8,8 @@ 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";
import ChatChannel from "discourse/plugins/chat/discourse/models/chat-channel";
import User from "discourse/models/user";
export default Component.extend({
chat: service(),
@ -19,6 +20,7 @@ export default Component.extend({
loading: false,
chatChannelsManager: service(),
router: service(),
focusedRow: null,
didInsertElement() {
this._super(...arguments);
@ -53,19 +55,16 @@ export default Component.extend({
} 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);
}
});
if (channel) {
this.set("focusedRow", channel);
}
}
},
@bind
onKeyUp(e) {
if (e.key === "Enter") {
let focusedChannel = this.channels.find((c) => c.focused);
let focusedChannel = this.channels.find((c) => c === this.focusedRow);
this.switchChannel(focusedChannel);
e.preventDefault();
} else if (e.key === "ArrowDown") {
@ -78,16 +77,17 @@ export default Component.extend({
},
arrowNavigateChannels(direction) {
const indexOfFocused = this.channels.findIndex((c) => c.focused);
const indexOfFocused = this.channels.findIndex(
(c) => c === this.focusedRow
);
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);
this.set("focusedRow", nextChannel);
}
} else {
this.channels[0].set("focused", true);
this.set("focusedRow", this.channels[0]);
}
schedule("afterRender", () => {
@ -100,7 +100,7 @@ export default Component.extend({
@action
switchChannel(channel) {
if (channel.user) {
if (channel instanceof User) {
return this.fetchOrCreateChannelForUser(channel).then((response) => {
const newChannel = this.chatChannelsManager.store(response.channel);
return this.chatChannelsManager.follow(newChannel).then((c) => {
@ -145,21 +145,21 @@ export default Component.extend({
.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
}
});
let channels = searchModel.public_channels
.concat(searchModel.direct_message_channels, searchModel.users)
.map((c) => {
if (
c.chatable_type === "DirectMessage" ||
c.chatable_type === "Category"
) {
return ChatChannel.create(c);
}
return User.create(c);
});
this.setProperties({
channels: channels.map((channel) => {
return channel.user
? ChatChannel.create(channel)
: this.chatChannelsManager.store(channel);
}),
channels,
loading: false,
});
this.focusFirstChannel(this.channels);
@ -188,8 +188,11 @@ export default Component.extend({
},
focusFirstChannel(channels) {
channels.forEach((c) => c.set("focused", false));
channels[0]?.set("focused", true);
if (channels[0]) {
this.set("focusedRow", channels[0]);
} else {
this.set("focusedRow", null);
}
},
getChannelsWithFilter(filter, opts = { excludeActiveChannel: true }) {

View File

@ -12,7 +12,7 @@
@value={{this.channel.currentUserMembership.muted}}
@valueProperty="value"
@class="channel-settings-view__muted-selector"
@onChange={{action (fn this.saveNotificationSettings "muted")}}
@onChange={{fn this.saveNotificationSettings "muted" "muted"}}
/>
</div>
</div>
@ -22,17 +22,19 @@
<label class="chat-form__label">
<span>{{i18n "chat.settings.desktop_notification_level"}}</span>
<ChatChannelSettingsSavedIndicator
@property={{this.channel.currentUserMembership.desktop_notification_level}}
@property={{this.channel.currentUserMembership.desktopNotificationLevel}}
/>
</label>
<div class="chat-form__control">
<ComboBox
@content={{this.notificationLevels}}
@value={{this.channel.currentUserMembership.desktop_notification_level}}
@value={{this.channel.currentUserMembership.desktopNotificationLevel}}
@valueProperty="value"
@class="channel-settings-view__desktop-notification-level-selector"
@onChange={{action
(fn this.saveNotificationSettings "desktop_notification_level")
@onChange={{fn
this.saveNotificationSettings
"desktopNotificationLevel"
"desktop_notification_level"
}}
/>
</div>
@ -42,17 +44,19 @@
<label class="chat-form__label">
<span>{{i18n "chat.settings.mobile_notification_level"}}</span>
<ChatChannelSettingsSavedIndicator
@property={{this.channel.currentUserMembership.mobile_notification_level}}
@property={{this.channel.currentUserMembership.mobileNotificationLevel}}
/>
</label>
<div class="chat-form__control">
<ComboBox
@content={{this.notificationLevels}}
@value={{this.channel.currentUserMembership.mobile_notification_level}}
@value={{this.channel.currentUserMembership.mobileNotificationLevel}}
@valueProperty="value"
@class="channel-settings-view__mobile-notification-level-selector"
@onChange={{action
(fn this.saveNotificationSettings "mobile_notification_level")
@onChange={{fn
this.saveNotificationSettings
"mobileNotificationLevel"
"mobile_notification_level"
}}
/>
</div>
@ -74,16 +78,16 @@
<label class="chat-form__label">
<span>{{i18n "chat.settings.auto_join_users_label"}}</span>
<ChatChannelSettingsSavedIndicator
@property={{this.channel.auto_join_users}}
@property={{this.channel.autoJoinUsers}}
/>
</label>
<ComboBox
@content={{this.autoAddUsersOptions}}
@value={{this.channel.auto_join_users}}
@value={{this.channel.autoJoinUsers}}
@valueProperty="value"
@class="channel-settings-view__auto-join-selector"
@onChange={{action
(fn this.onToggleAutoJoinUsers this.channel.auto_join_users)
(fn this.onToggleAutoJoinUsers this.channel.autoJoinUsers)
}}
/>
<p class="chat-form__description -autojoin">
@ -102,12 +106,12 @@
<label class="chat-form__label">
<span>{{i18n "chat.settings.channel_wide_mentions_label"}}</span>
<ChatChannelSettingsSavedIndicator
@property={{this.channel.allow_channel_wide_mentions}}
@property={{this.channel.allowChannelWideMentions}}
/>
</label>
<ComboBox
@content={{this.channelWideMentionsOptions}}
@value={{this.channel.allow_channel_wide_mentions}}
@value={{this.channel.allowChannelWideMentions}}
@valueProperty="value"
@class="channel-settings-view__channel-wide-mentions-selector"
@onChange={{this.onToggleChannelWideMentions}}

View File

@ -3,7 +3,6 @@ import { action, computed } from "@ember/object";
import { inject as service } from "@ember/service";
import showModal from "discourse/lib/show-modal";
import I18n from "I18n";
import { Promise } from "rsvp";
import { reads } from "@ember/object/computed";
const NOTIFICATION_LEVELS = [
@ -79,29 +78,18 @@ export default class ChatChannelSettingsView extends Component {
}
@action
saveNotificationSettings(key, value) {
if (this.channel[key] === value) {
saveNotificationSettings(frontendKey, backendKey, newValue) {
if (this.channel.currentUserMembership[frontendKey] === newValue) {
return;
}
const settings = {};
settings[key] = value;
settings[backendKey] = newValue;
return this.chatApi
.updateCurrentUserChannelNotificationsSettings(this.channel.id, settings)
.then((result) => {
[
"muted",
"desktop_notification_level",
"mobile_notification_level",
].forEach((property) => {
if (
result.membership[property] !==
this.channel.currentUserMembership[property]
) {
this.channel.currentUserMembership[property] =
result.membership[property];
}
});
this.channel.currentUserMembership[frontendKey] =
result.membership[backendKey];
});
}
@ -125,7 +113,7 @@ export default class ChatChannelSettingsView extends Component {
@action
onToggleAutoJoinUsers() {
if (!this.channel.auto_join_users) {
if (!this.channel.autoJoinUsers) {
this.onEnableAutoJoinUsers();
} else {
this.onDisableAutoJoinUsers();
@ -134,43 +122,61 @@ export default class ChatChannelSettingsView extends Component {
@action
onToggleChannelWideMentions() {
const newValue = !this.channel.allowChannelWideMentions;
if (this.channel.allowChannelWideMentions === newValue) {
return;
}
return this._updateChannelProperty(
this.channel,
"allow_channel_wide_mentions",
!this.channel.allow_channel_wide_mentions
);
newValue
).then((result) => {
this.channel.allowChannelWideMentions =
result.channel.allow_channel_wide_mentions;
});
}
onDisableAutoJoinUsers() {
return this._updateChannelProperty(this.channel, "auto_join_users", false);
if (this.channel.autoJoinUsers === false) {
return;
}
return this._updateChannelProperty(
this.channel,
"auto_join_users",
false
).then((result) => {
this.channel.autoJoinUsers = result.channel.auto_join_users;
});
}
onEnableAutoJoinUsers() {
if (this.channel.autoJoinUsers === true) {
return;
}
this.dialog.confirm({
message: I18n.t("chat.settings.auto_join_users_warning", {
category: this.channel.chatable.name,
}),
didConfirm: () =>
this._updateChannelProperty(this.channel, "auto_join_users", true),
this._updateChannelProperty(this.channel, "auto_join_users", true).then(
(result) => {
this.channel.autoJoinUsers = result.channel.auto_join_users;
}
),
});
}
_updateChannelProperty(channel, property, value) {
if (channel[property] === value) {
return Promise.resolve();
}
const payload = {};
payload[property] = value;
return this.chatApi
.updateChannel(channel.id, payload)
.then((result) => {
channel.set(property, result.channel[property]);
})
.catch((event) => {
if (event.jqXHR?.responseJSON?.errors) {
this.flash(event.jqXHR.responseJSON.errors.join("\n"), "error");
}
});
return this.chatApi.updateChannel(channel.id, payload).catch((event) => {
if (event.jqXHR?.responseJSON?.errors) {
this.flash(event.jqXHR.responseJSON.errors.join("\n"), "error");
}
});
}
}

View File

@ -1,21 +1,21 @@
{{#if this.channel.isDraft}}
{{#if @channel.isDraft}}
<div class="chat-channel-title is-draft">
<span class="chat-channel-title__name">{{this.channel.title}}</span>
<span class="chat-channel-title__name">{{@channel.title}}</span>
{{#if (has-block)}}
{{yield}}
{{/if}}
</div>
{{else}}
{{#if this.channel.isDirectMessageChannel}}
{{#if @channel.isDirectMessageChannel}}
<div class="chat-channel-title is-dm">
<div class="chat-channel-title__avatar">
{{#if this.multiDm}}
<span class="chat-channel-title__users-count">
{{this.channel.chatable.users.length}}
{{@channel.chatable.users.length}}
</span>
{{else}}
<ChatUserAvatar @user={{this.channel.chatable.users.firstObject}} />
<ChatUserAvatar @user={{@channel.chatable.users.firstObject}} />
{{/if}}
</div>
@ -24,12 +24,12 @@
{{#if this.multiDm}}
<span class="chat-channel-title__name">{{this.usernames}}</span>
{{else}}
{{#let this.channel.chatable.users.firstObject as |user|}}
{{#let @channel.chatable.users.firstObject as |user|}}
<span class="chat-channel-title__name">{{user.username}}</span>
{{#if this.showUserStatus}}
<UserStatusMessage
@class="chat-channel-title__user-status-message"
@status={{this.channel.chatable.users.firstObject.status}}
@status={{@channel.chatable.users.firstObject.status}}
@showDescription={{if this.site.mobileView "true"}}
/>
{{/if}}
@ -48,19 +48,19 @@
{{yield}}
{{/if}}
</div>
{{else if this.channel.isCategoryChannel}}
{{else if @channel.isCategoryChannel}}
<div class="chat-channel-title is-category">
<span
class="chat-channel-title__category-badge"
style={{this.channelColorStyle}}
>
{{d-icon "d-chat"}}
{{#if this.channel.chatable.read_restricted}}
{{#if @channel.chatable.read_restricted}}
{{d-icon "lock" class="chat-channel-title__restricted-category-icon"}}
{{/if}}
</span>
<span class="chat-channel-title__name">
{{replace-emoji this.channel.title}}
{{replace-emoji @channel.title}}
</span>
{{#if (has-block)}}

View File

@ -1,33 +1,24 @@
import Component from "@ember/component";
import Component from "@glimmer/component";
import { htmlSafe } from "@ember/template";
import { computed } from "@ember/object";
import { gt, reads } from "@ember/object/computed";
export default class ChatChannelTitle extends Component {
tagName = "";
channel = null;
get users() {
return this.args.channel.chatable.users;
}
@reads("channel.chatable.users.[]") users;
@gt("users.length", 1) multiDm;
get multiDm() {
return this.users.length > 1;
}
@computed("users")
get usernames() {
return this.users.mapBy("username").join(", ");
}
@computed("channel.chatable.color")
get channelColorStyle() {
return htmlSafe(`color: #${this.channel.chatable.color}`);
return htmlSafe(`color: #${this.args.channel.chatable.color}`);
}
@computed(
"channel.chatable.users.length",
"channel.chatable.users.@each.status"
)
get showUserStatus() {
return !!(
this.channel.chatable.users.length === 1 &&
this.channel.chatable.users[0].status
);
return !!(this.users.length === 1 && this.users[0].status);
}
}

View File

@ -1,16 +1,16 @@
{{#if (gt @channel.currentUserMembership.unread_count 0)}}
{{#if (gt @channel.currentUserMembership.unreadCount 0)}}
<div
class={{concat-class
"chat-channel-unread-indicator"
(if
(or
@channel.isDirectMessageChannel
(gt @channel.currentUserMembership.unread_mentions 0)
(gt @channel.currentUserMembership.unreadMentions 0)
)
"urgent"
)
}}
>
<div class="number">{{@channel.currentUserMembership.unread_count}}</div>
<div class="number">{{@channel.currentUserMembership.unreadCount}}</div>
</div>
{{/if}}

View File

@ -165,7 +165,7 @@ export default class ChatLivePane extends Component {
findArgs["targetMessageId"] = this.requestedTargetMessageId;
} else if (fetchingFromLastRead) {
findArgs["targetMessageId"] =
this.args.channel.currentUserMembership.last_read_message_id;
this.args.channel.currentUserMembership.lastReadMessageId;
}
return this.chatApi
@ -346,7 +346,7 @@ export default class ChatLivePane extends Component {
if (
!foundFirstNew &&
messageData.id >
this.args.channel.currentUserMembership.last_read_message_id &&
this.args.channel.currentUserMembership.lastReadMessageId &&
!channel.messages.some((m) => m.newest)
) {
foundFirstNew = true;
@ -444,7 +444,7 @@ export default class ChatLivePane extends Component {
}
const lastReadId =
this.args.channel.currentUserMembership?.last_read_message_id;
this.args.channel.currentUserMembership?.lastReadMessageId;
let lastUnreadVisibleMessage = this.args.channel.visibleMessages.findLast(
(message) => !lastReadId || message.id > lastReadId
);

View File

@ -93,7 +93,7 @@
{{#if this.shouldRenderReplyingIndicator}}
<div class="chat-replying-indicator-container">
<ChatReplyingIndicator @chatChannel={{@channel}} />
<ChatReplyingIndicator @channel={{@channel}} />
</div>
{{/if}}

View File

@ -3,6 +3,7 @@ import { inject as service } from "@ember/service";
import Component from "@ember/component";
import { action } from "@ember/object";
import { cloneJSON } from "discourse-common/lib/object";
export default class ChatDraftChannelScreen extends Component {
@service chat;
@service router;
@ -20,7 +21,7 @@ export default class ChatDraftChannelScreen extends Component {
@action
onSwitchFromDraftChannel(channel) {
channel.set("isDraft", false);
channel.isDraft = false;
}
_fetchPreviewedChannel(users) {
@ -29,20 +30,16 @@ export default class ChatDraftChannelScreen extends Component {
return this.chat
.getDmChannelForUsernames(users.mapBy("username"))
.then((response) => {
this.set(
"previewedChannel",
ChatChannel.create(
Object.assign({}, response.channel, { isDraft: true })
)
);
const channel = ChatChannel.create(response.channel);
channel.isDraft = true;
this.set("previewedChannel", channel);
})
.catch((error) => {
if (error?.jqXHR?.status === 404) {
this.set(
"previewedChannel",
ChatChannel.create({
chatable: { users: cloneJSON(users) },
isDraft: true,
ChatChannel.createDirectMessageChannelDraft({
users: cloneJSON(users),
})
);
}

View File

@ -1,10 +1,21 @@
<div class="chat-replying-indicator">
{{#if this.shouldDisplay}}
<span class="chat-replying-indicator__text">{{this.text}}</span>
<span class="chat-replying-indicator__wave">
<span class="chat-replying-indicator__dot">.</span>
<span class="chat-replying-indicator__dot">.</span>
<span class="chat-replying-indicator__dot">.</span>
</span>
{{/if}}
</div>
{{#if @channel}}
<div
class={{concat-class
"chat-replying-indicator"
(if this.presenceChannel.subscribed "is-subscribed")
}}
{{did-insert this.subscribe}}
{{did-update this.handleDraft @channel.isDraft}}
{{did-update this.subscribe this.channelName}}
{{will-destroy this.teardown}}
>
{{#if this.shouldRender}}
<span class="chat-replying-indicator__text">{{this.text}}</span>
<span class="chat-replying-indicator__wave">
<span class="chat-replying-indicator__dot">.</span>
<span class="chat-replying-indicator__dot">.</span>
<span class="chat-replying-indicator__dot">.</span>
</span>
{{/if}}
</div>
{{/if}}

View File

@ -1,89 +1,93 @@
import { isBlank, isPresent } from "@ember/utils";
import Component from "@ember/component";
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
import { fmt } from "discourse/lib/computed";
import { next } from "@ember/runloop";
import { action } from "@ember/object";
import { tracked } from "@glimmer/tracking";
export default Component.extend({
tagName: "",
presence: service(),
presenceChannel: null,
chatChannel: null,
export default class ChatReplyingIndicator extends Component {
@service currentUser;
@service presence;
@discourseComputed("presenceChannel.users.[]")
usernames(users) {
return users
?.filter((u) => u.id !== this.currentUser.id)
?.mapBy("username");
},
@tracked presenceChannel = null;
@tracked users = [];
@discourseComputed("usernames.[]")
text(usernames) {
if (isBlank(usernames)) {
@action
async subscribe() {
this.presenceChannel = this.presence.getChannel(this.channelName);
this.presenceChannel.on("change", this.handlePresenceChange);
await this.presenceChannel.subscribe();
}
@action
async resubscribe() {
await this.teardown();
await this.subscribe();
}
@action
handlePresenceChange(presenceChannel) {
this.users = presenceChannel.users || [];
}
@action
async handleDraft() {
if (this.args.channel.isDraft) {
await this.teardown();
} else {
await this.resubscribe();
}
}
@action
async teardown() {
if (this.presenceChannel) {
await this.presenceChannel.unsubscribe();
}
}
get usernames() {
return this.users
.filter((u) => u.id !== this.currentUser.id)
.mapBy("username");
}
get text() {
if (isBlank(this.usernames)) {
return;
}
if (usernames.length === 1) {
if (this.usernames.length === 1) {
return I18n.t("chat.replying_indicator.single_user", {
username: usernames[0],
username: this.usernames[0],
});
}
if (usernames.length < 4) {
const lastUsername = usernames.pop();
const commaSeparatedUsernames = usernames.join(
I18n.t("word_connector.comma")
);
if (this.usernames.length < 4) {
const lastUsername = this.usernames[this.usernames.length - 1];
const commaSeparatedUsernames = this.usernames
.slice(0, this.usernames.length - 1)
.join(I18n.t("word_connector.comma"));
return I18n.t("chat.replying_indicator.multiple_users", {
commaSeparatedUsernames,
lastUsername,
});
}
const commaSeparatedUsernames = usernames
const commaSeparatedUsernames = this.usernames
.slice(0, 2)
.join(I18n.t("word_connector.comma"));
return I18n.t("chat.replying_indicator.many_users", {
commaSeparatedUsernames,
count: usernames.length - 2,
count: this.usernames.length - 2,
});
},
}
@discourseComputed("usernames.[]")
shouldDisplay(usernames) {
return isPresent(usernames);
},
get shouldRender() {
return isPresent(this.usernames);
}
channelName: fmt("chatChannel.id", "/chat-reply/%@"),
didReceiveAttrs() {
this._super(...arguments);
if (!this.chatChannel || this.chatChannel.isDraft) {
this.presenceChannel?.unsubscribe();
return;
}
if (this.presenceChannel?.name !== this.channelName) {
this.presenceChannel?.unsubscribe();
next(() => {
if (this.isDestroyed || this.isDestroying) {
return;
}
const presenceChannel = this.presence.getChannel(this.channelName);
this.set("presenceChannel", presenceChannel);
presenceChannel.subscribe();
});
}
},
willDestroyElement() {
this._super(...arguments);
this.presenceChannel?.unsubscribe();
},
});
get channelName() {
return `/chat-reply/${this.args.channel.id}`;
}
}

View File

@ -75,14 +75,14 @@ export default class ChatSelectionManager extends Component {
const openOpts = {};
if (this.chatChannel.isCategoryChannel) {
openOpts.categoryId = this.chatChannel.chatable_id;
openOpts.categoryId = this.chatChannel.chatableId;
}
if (this.site.mobileView) {
// go to the relevant chatable (e.g. category) and open the
// composer to insert text
if (this.chatChannel.chatable_url) {
this.router.transitionTo(this.chatChannel.chatable_url);
if (this.chatChannel.chatableUrl) {
this.router.transitionTo(this.chatChannel.chatableUrl);
}
await composer.focusComposer({

View File

@ -8,7 +8,7 @@ import { INPUT_DELAY } from "discourse-common/config/environment";
import { inject as service } from "@ember/service";
import { schedule } from "@ember/runloop";
import { gt, not } from "@ember/object/computed";
import { createDirectMessageChannelDraft } from "discourse/plugins/chat/discourse/models/chat-channel";
import ChatChannel from "discourse/plugins/chat/discourse/models/chat-channel";
export default Component.extend({
tagName: "",
@ -29,7 +29,7 @@ export default Component.extend({
this.set("users", []);
this.set("selectedUsers", []);
this.set("channel", createDirectMessageChannelDraft());
this.set("channel", ChatChannel.createDirectMessageChannelDraft());
},
didInsertElement() {

View File

@ -154,7 +154,7 @@ export default {
}
const highlightable = [`@${this.currentUser.username}`];
if (chatChannel.allow_channel_wide_mentions) {
if (chatChannel.allowChannelWideMentions) {
highlightable.push(...MENTION_KEYWORDS.map((k) => `@${k}`));
}

View File

@ -88,13 +88,13 @@ export default {
}
get suffixValue() {
return this.channel.currentUserMembership.unread_count > 0
return this.channel.currentUserMembership.unreadCount > 0
? "circle"
: "";
}
get suffixCSSClass() {
return this.channel.currentUserMembership.unread_mentions > 0
return this.channel.currentUserMembership.unreadMentions > 0
? "urgent"
: "unread";
}
@ -282,7 +282,7 @@ export default {
}
get suffixValue() {
return this.channel.currentUserMembership.unread_count > 0
return this.channel.currentUserMembership.unreadCount > 0
? "circle"
: "";
}

View File

@ -0,0 +1,21 @@
import { tracked } from "@glimmer/tracking";
export default class ChatChannelArchive {
static create(args = {}) {
return new ChatChannelArchive(args);
}
@tracked failed;
@tracked completed;
@tracked messages;
@tracked topicId;
@tracked totalMessages;
constructor(args = {}) {
this.failed = args.archive_failed;
this.completed = args.archive_completed;
this.messages = args.archived_messages;
this.topicId = args.archive_topic_id;
this.totalMessages = args.total_messages;
}
}

View File

@ -1,5 +1,3 @@
import RestModel from "discourse/models/rest";
import User from "discourse/models/user";
import UserChatChannelMembership from "discourse/plugins/chat/discourse/models/user-chat-channel-membership";
import { ajax } from "discourse/lib/ajax";
import { escapeExpression } from "discourse/lib/utilities";
@ -10,6 +8,9 @@ import ChatMessagesManager from "discourse/plugins/chat/discourse/lib/chat-messa
import { getOwner } from "discourse-common/lib/get-owner";
import guid from "pretty-text/guid";
import ChatThread from "discourse/plugins/chat/discourse/models/chat-thread";
import ChatDirectMessage from "discourse/plugins/chat/discourse/models/chat-direct-message";
import ChatChannelArchive from "discourse/plugins/chat/discourse/models/chat-channel-archive";
import Category from "discourse/models/category";
export const CHATABLE_TYPES = {
directMessageChannel: "DirectMessage",
@ -49,25 +50,79 @@ const READONLY_STATUSES = [
CHANNEL_STATUSES.archived,
];
export default class ChatChannel extends RestModel {
export default class ChatChannel {
static create(args = {}) {
return new ChatChannel(args);
}
static createDirectMessageChannelDraft(args = {}) {
const channel = ChatChannel.create({
chatable_type: CHATABLE_TYPES.directMessageChannel,
chatable: {
users: args.users || [],
},
});
channel.isDraft = true;
return channel;
}
@tracked currentUserMembership = null;
@tracked isDraft = false;
@tracked title;
@tracked slug;
@tracked description;
@tracked chatableType;
@tracked status;
@tracked activeThread;
@tracked activeThread = null;
@tracked lastMessageSentAt;
@tracked canDeleteOthers;
@tracked canDeleteSelf;
@tracked canFlag;
@tracked canModerate;
@tracked userSilenced;
@tracked draft;
@tracked draft = null;
@tracked meta;
@tracked chatableType;
@tracked chatableUrl;
@tracked autoJoinUsers = false;
@tracked allowChannelWideMentions = true;
@tracked membershipsCount = 0;
@tracked archive;
threadsManager = new ChatThreadsManager(getOwner(this));
messagesManager = new ChatMessagesManager(getOwner(this));
constructor(args = {}) {
this.id = args.id;
this.chatableId = args.chatable_id;
this.chatableUrl = args.chatable_url;
this.chatableType = args.chatable_type;
this.membershipsCount = args.memberships_count;
this.meta = args.meta;
this.slug = args.slug;
this.title = args.title;
this.status = args.status;
this.canDeleteSelf = args.can_delete_self;
this.canDeleteOthers = args.can_delete_others;
this.canFlag = args.can_flag;
this.userSilenced = args.user_silenced;
this.canModerate = args.can_moderate;
this.description = args.description;
this.lastMessageSentAt = args.last_message_sent_at;
this.threadingEnabled = args.threading_enabled;
this.autoJoinUsers = args.auto_join_users;
this.allowChannelWideMentions = args.allow_channel_wide_mentions;
this.chatable = this.isDirectMessageChannel
? ChatDirectMessage.create(args)
: Category.create(args.chatable);
this.currentUserMembership = UserChatChannelMembership.create(
args.current_user_membership
);
if (args.archive_completed || args.archive_failed) {
this.archive = ChatChannelArchive.create(args);
}
}
findIndexOfMessage(id) {
return this.messagesManager.findIndexOfMessage(id);
}
@ -223,14 +278,14 @@ export default class ChatChannel extends RestModel {
updateMembership(membership) {
this.currentUserMembership.following = membership.following;
this.currentUserMembership.last_read_message_id =
this.currentUserMembership.lastReadMessage_id =
membership.last_read_message_id;
this.currentUserMembership.desktop_notification_level =
this.currentUserMembership.desktopNotificationLevel =
membership.desktop_notification_level;
this.currentUserMembership.mobile_notification_level =
this.currentUserMembership.mobileNotificationLevel =
membership.mobile_notification_level;
this.currentUserMembership.unread_count = membership.unread_count;
this.currentUserMembership.unread_mentions = membership.unread_mentions;
this.currentUserMembership.unreadCount = membership.unread_count;
this.currentUserMembership.unreadMentions = membership.unread_mentions;
this.currentUserMembership.muted = membership.muted;
}
@ -239,7 +294,7 @@ export default class ChatChannel extends RestModel {
return;
}
if (this.currentUserMembership.last_read_message_id >= messageId) {
if (this.currentUserMembership.lastReadMessageId >= messageId) {
return;
}
@ -250,59 +305,3 @@ export default class ChatChannel extends RestModel {
});
}
}
ChatChannel.reopenClass({
create(args) {
args = args || {};
this._initUserModels(args);
this._initUserMembership(args);
this._remapKey(args, "chatable_type", "chatableType");
this._remapKey(args, "memberships_count", "membershipsCount");
this._remapKey(args, "last_message_sent_at", "lastMessageSentAt");
this._remapKey(args, "threading_enabled", "threadingEnabled");
return this._super(args);
},
_remapKey(obj, oldKey, newKey) {
delete Object.assign(obj, { [newKey]: obj[oldKey] })[oldKey];
},
_initUserModels(args) {
if (args.chatable?.users?.length) {
for (let i = 0; i < args.chatable?.users?.length; i++) {
const userData = args.chatable.users[i];
args.chatable.users[i] = User.create(userData);
}
}
},
_initUserMembership(args) {
if (args.currentUserMembership instanceof UserChatChannelMembership) {
return;
}
args.currentUserMembership = UserChatChannelMembership.create(
args.current_user_membership || {
following: false,
muted: false,
unread_count: 0,
unread_mentions: 0,
}
);
delete args.current_user_membership;
},
});
export function createDirectMessageChannelDraft() {
return ChatChannel.create({
isDraft: true,
chatable_type: CHATABLE_TYPES.directMessageChannel,
chatable: {
users: [],
},
});
}

View File

@ -0,0 +1,26 @@
import User from "discourse/models/user";
import { tracked } from "@glimmer/tracking";
export default class ChatDirectMessage {
static create(args = {}) {
return new ChatDirectMessage(args);
}
@tracked id;
@tracked users = null;
constructor(args = {}) {
this.id = args.chatable.id;
this.users = this.#initUsers(args.chatable.users || []);
}
#initUsers(users) {
return users.map((user) => {
if (!user || user instanceof User) {
return user;
}
return User.create(user);
});
}
}

View File

@ -166,7 +166,7 @@ export default class ChatMessage {
}
get read() {
return this.channel.currentUserMembership?.last_read_message_id >= this.id;
return this.channel.currentUserMembership?.lastReadMessageId >= this.id;
}
get firstMessageOfTheDayAt() {

View File

@ -1,28 +1,36 @@
import RestModel from "discourse/models/rest";
import { tracked } from "@glimmer/tracking";
import User from "discourse/models/user";
export default class UserChatChannelMembership extends RestModel {
export default class UserChatChannelMembership {
static create(args = {}) {
return new UserChatChannelMembership(args);
}
@tracked following = false;
@tracked muted = false;
@tracked unread_count = 0;
@tracked unread_mentions = 0;
@tracked desktop_notification_level = null;
@tracked mobile_notification_level = null;
@tracked last_read_message_id = null;
}
@tracked unreadCount = 0;
@tracked unreadMentions = 0;
@tracked desktopNotificationLevel = null;
@tracked mobileNotificationLevel = null;
@tracked lastReadMessageId = null;
@tracked user = null;
UserChatChannelMembership.reopenClass({
create(args) {
args = args || {};
this._initUser(args);
return this._super(args);
},
constructor(args = {}) {
this.following = args.following;
this.muted = args.muted;
this.unreadCount = args.unread_count;
this.unreadMentions = args.unread_mentions;
this.desktopNotificationLevel = args.desktop_notification_level;
this.mobileNotificationLevel = args.mobile_notification_level;
this.lastReadMessageId = args.last_read_message_id;
this.user = this.#initUserModel(args.user);
}
_initUser(args) {
if (!args.user || args.user instanceof User) {
return;
#initUserModel(user) {
if (!user || user instanceof User) {
return user;
}
args.user = User.create(args.user);
},
});
return User.create(user);
}
}

View File

@ -61,11 +61,10 @@ export default class ChatChannelsManager extends Service {
return this.chatApi.followChannel(model.id).then((membership) => {
model.currentUserMembership.following = membership.following;
model.currentUserMembership.muted = membership.muted;
model.currentUserMembership.desktop_notification_level =
membership.desktop_notification_level;
model.currentUserMembership.mobile_notification_level =
membership.mobile_notification_level;
model.currentUserMembership.desktopNotificationLevel =
membership.desktopNotificationLevel;
model.currentUserMembership.mobileNotificationLevel =
membership.mobileNotificationLevel;
return model;
});
} else {
@ -97,7 +96,7 @@ export default class ChatChannelsManager extends Service {
get unreadCount() {
let count = 0;
this.publicMessageChannels.forEach((channel) => {
count += channel.currentUserMembership.unread_count || 0;
count += channel.currentUserMembership.unreadCount || 0;
});
return count;
}
@ -106,9 +105,9 @@ export default class ChatChannelsManager extends Service {
let count = 0;
this.channels.forEach((channel) => {
if (channel.isDirectMessageChannel) {
count += channel.currentUserMembership.unread_count || 0;
count += channel.currentUserMembership.unreadCount || 0;
}
count += channel.currentUserMembership.unread_mentions || 0;
count += channel.currentUserMembership.unreadMentions || 0;
});
return count;
}
@ -159,8 +158,8 @@ export default class ChatChannelsManager extends Service {
#sortDirectMessageChannels(channels) {
return channels.sort((a, b) => {
const unreadCountA = a.currentUserMembership.unread_count || 0;
const unreadCountB = b.currentUserMembership.unread_count || 0;
const unreadCountA = a.currentUserMembership.unreadCount || 0;
const unreadCountB = b.currentUserMembership.unreadCount || 0;
if (unreadCountA === unreadCountB) {
return new Date(a.lastMessageSentAt) > new Date(b.lastMessageSentAt)
? -1

View File

@ -2,6 +2,7 @@ import Service, { inject as service } from "@ember/service";
import I18n from "I18n";
import { bind } from "discourse-common/utils/decorators";
import { CHANNEL_STATUSES } from "discourse/plugins/chat/discourse/models/chat-channel";
import ChatChannelArchive from "../models/chat-channel-archive";
export default class ChatSubscriptionsManager extends Service {
@service store;
@ -125,13 +126,7 @@ export default class ChatSubscriptionsManager extends Service {
return;
}
channel.setProperties({
archive_failed: busData.archive_failed,
archive_completed: busData.archive_completed,
archived_messages: busData.archived_messages,
archive_topic_id: busData.archive_topic_id,
total_messages: busData.total_messages,
});
channel.archive = ChatChannelArchive.create(busData);
});
}
@ -139,8 +134,8 @@ export default class ChatSubscriptionsManager extends Service {
_onNewMentions(busData) {
this.chatChannelsManager.find(busData.channel_id).then((channel) => {
const membership = channel.currentUserMembership;
if (busData.message_id > membership?.last_read_message_id) {
membership.unread_mentions = (membership.unread_mentions || 0) + 1;
if (busData.message_id > membership?.lastReadMessageId) {
membership.unreadMentions = (membership.unreadMentions || 0) + 1;
}
});
}
@ -186,20 +181,18 @@ export default class ChatSubscriptionsManager extends Service {
this.chatChannelsManager.find(busData.channel_id).then((channel) => {
if (busData.user_id === this.currentUser.id) {
// User sent message, update tracking state to no unread
channel.currentUserMembership.last_read_message_id = busData.message_id;
channel.currentUserMembership.lastReadMessageId = busData.message_id;
} else {
// Ignored user sent message, update tracking state to no unread
if (this.currentUser.ignored_users.includes(busData.username)) {
channel.currentUserMembership.last_read_message_id =
busData.message_id;
channel.currentUserMembership.lastReadMessageId = busData.message_id;
} else {
// Message from other user. Increment trackings state
if (
busData.message_id >
(channel.currentUserMembership.last_read_message_id || 0)
(channel.currentUserMembership.lastReadMessageId || 0)
) {
channel.currentUserMembership.unread_count =
channel.currentUserMembership.unread_count + 1;
channel.currentUserMembership.unreadCount++;
}
}
}
@ -256,11 +249,10 @@ export default class ChatSubscriptionsManager extends Service {
@bind
_updateChannelTrackingData(channelId, trackingData) {
this.chatChannelsManager.find(channelId).then((channel) => {
channel.currentUserMembership.last_read_message_id =
channel.currentUserMembership.lastReadMessageId =
trackingData.last_read_message_id;
channel.currentUserMembership.unread_count = trackingData.unread_count;
channel.currentUserMembership.unread_mentions =
trackingData.mention_count;
channel.currentUserMembership.unreadCount = trackingData.unread_count;
channel.currentUserMembership.unreadMentions = trackingData.mention_count;
});
}
@ -289,7 +281,7 @@ export default class ChatSubscriptionsManager extends Service {
channel.isDirectMessageChannel &&
!channel.currentUserMembership.following
) {
channel.currentUserMembership.unread_count = 1;
channel.currentUserMembership.unreadCount = 1;
}
this.chatChannelsManager.follow(channel);
@ -341,9 +333,7 @@ export default class ChatSubscriptionsManager extends Service {
.find(busData.chat_channel_id, { fetchIfNotFound: false })
.then((channel) => {
if (channel) {
channel.setProperties({
memberships_count: busData.memberships_count,
});
channel.membershipsCount = busData.memberships_count;
this.appEvents.trigger("chat:refresh-channel-members");
}
});
@ -353,11 +343,9 @@ export default class ChatSubscriptionsManager extends Service {
_onChannelEdits(busData) {
this.chatChannelsManager.find(busData.chat_channel_id).then((channel) => {
if (channel) {
channel.setProperties({
title: busData.name,
description: busData.description,
slug: busData.slug,
});
channel.title = busData.name;
channel.description = busData.description;
channel.slug = busData.slug;
}
});
}
@ -365,15 +353,15 @@ export default class ChatSubscriptionsManager extends Service {
@bind
_onChannelStatus(busData) {
this.chatChannelsManager.find(busData.chat_channel_id).then((channel) => {
channel.set("status", busData.status);
channel.status = busData.status;
// it is not possible for the user to set their last read message id
// if the channel has been archived, because all the messages have
// been deleted. we don't want them seeing the blue dot anymore so
// just completely reset the unreads
if (busData.status === CHANNEL_STATUSES.archived) {
channel.currentUserMembership.unread_count = 0;
channel.currentUserMembership.unread_mentions = 0;
channel.currentUserMembership.unreadCount = 0;
channel.currentUserMembership.unreadMentions = 0;
}
});
}

View File

@ -283,7 +283,7 @@ export default class Chat extends Service {
const membership = channel.currentUserMembership;
if (channel.isDirectMessageChannel) {
if (!dmChannelWithUnread && membership.unread_count > 0) {
if (!dmChannelWithUnread && membership.unreadCount > 0) {
dmChannelWithUnread = channel.id;
} else if (!dmChannel) {
dmChannel = channel.id;
@ -292,7 +292,7 @@ export default class Chat extends Service {
if (membership.unread_mentions > 0) {
publicChannelWithMention = channel.id;
return; // <- We have a public channel with a mention. Break and return this.
} else if (!publicChannelWithUnread && membership.unread_count > 0) {
} else if (!publicChannelWithUnread && membership.unreadCount > 0) {
publicChannelWithUnread = channel.id;
} else if (
!defaultChannel &&

View File

@ -20,6 +20,7 @@ RSpec.describe "Channel selector modal", type: :system, js: true do
find("body").send_keys([key_modifier, "k"])
find("#chat-channel-selector-input").fill_in(with: channel_1.title)
find(".chat-channel-selection-row[data-id='#{channel_1.id}']").click
channel_page.send_message("Hello world")
expect(channel_page).to have_message(text: "Hello world")
@ -33,6 +34,7 @@ RSpec.describe "Channel selector modal", type: :system, js: true do
find("body").send_keys([key_modifier, "k"])
find("#chat-channel-selector-input").fill_in(with: user_1.username)
find(".chat-channel-selection-row[data-id='#{user_1.id}']").click
channel_page.send_message("Hello world")
expect(channel_page).to have_message(text: "Hello world")
@ -69,7 +71,6 @@ RSpec.describe "Channel selector modal", type: :system, js: true do
fab!(:channel_1) { Fabricate(:private_category_channel, group: group_1) }
it "it doesnt include limited access channel" do
chat_page.visit_channel(channel_1)
find("body").send_keys([key_modifier, "k"])
find("#chat-channel-selector-input").fill_in(with: channel_1.title)

View File

@ -126,11 +126,11 @@ RSpec.describe "Drawer", type: :system, js: true do
session.quit
end
expect(page).to have_content("onlyonce", count: 1)
expect(page).to have_content("onlyonce", count: 1, wait: 20)
chat_page.visit_channel(channel_2)
expect(page).to have_content("onlyonce", count: 0)
expect(page).to have_content("onlyonce", count: 0, wait: 20)
end
end
end

View File

@ -10,15 +10,13 @@ module("Discourse Chat | Component | chat-channel-card", function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function () {
this.set("channel", fabricators.chatChannel());
this.channel.set(
"description",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
);
this.channel = fabricators.chatChannel();
this.channel.description =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
});
test("escapes channel title", async function (assert) {
this.channel.set("title", "<div class='xss'>evil</div>");
this.channel.title = "<div class='xss'>evil</div>";
await render(hbs`<ChatChannelCard @channel={{this.channel}} />`);
@ -26,7 +24,7 @@ module("Discourse Chat | Component | chat-channel-card", function (hooks) {
});
test("escapes channel description", async function (assert) {
this.channel.set("description", "<div class='xss'>evil</div>");
this.channel.description = "<div class='xss'>evil</div>";
await render(hbs`<ChatChannelCard @channel={{this.channel}} />`);
@ -34,14 +32,14 @@ module("Discourse Chat | Component | chat-channel-card", function (hooks) {
});
test("Closed channel", async function (assert) {
this.channel.set("status", "closed");
this.channel.status = "closed";
await render(hbs`<ChatChannelCard @channel={{this.channel}} />`);
assert.true(exists(".chat-channel-card.-closed"));
});
test("Archived channel", async function (assert) {
this.channel.set("status", "archived");
this.channel.status = "archived";
await render(hbs`<ChatChannelCard @channel={{this.channel}} />`);
assert.true(exists(".chat-channel-card.-archived"));
@ -59,7 +57,7 @@ module("Discourse Chat | Component | chat-channel-card", function (hooks) {
});
test("Joined channel", async function (assert) {
this.channel.currentUserMembership.set("following", true);
this.channel.currentUserMembership.following = true;
await render(hbs`<ChatChannelCard @channel={{this.channel}} />`);
assert.strictEqual(
@ -77,7 +75,7 @@ module("Discourse Chat | Component | chat-channel-card", function (hooks) {
});
test("Memberships count", async function (assert) {
this.channel.set("membershipsCount", 4);
this.channel.membershipsCount = 4;
await render(hbs`<ChatChannelCard @channel={{this.channel}} />`);
assert.strictEqual(
@ -87,7 +85,7 @@ module("Discourse Chat | Component | chat-channel-card", function (hooks) {
});
test("No description", async function (assert) {
this.channel.set("description", null);
this.channel.description = null;
await render(hbs`<ChatChannelCard @channel={{this.channel}} />`);
assert.false(exists(".chat-channel-card__description"));
@ -118,7 +116,7 @@ module("Discourse Chat | Component | chat-channel-card", function (hooks) {
});
test("Read restricted chatable", async function (assert) {
this.channel.set("chatable.read_restricted", true);
this.channel.chatable.read_restricted = true;
await render(hbs`<ChatChannelCard @channel={{this.channel}} />`);
assert.true(exists(".d-icon-lock"));

View File

@ -5,20 +5,15 @@ import hbs from "htmlbars-inline-precompile";
import pretender from "discourse/tests/helpers/create-pretender";
import I18n from "I18n";
import { module, test } from "qunit";
import fabricators from "../helpers/fabricators";
module("Discourse Chat | Component | chat-channel-leave-btn", function (hooks) {
setupRenderingTest(hooks);
test("accepts an optional onLeaveChannel callback", async function (assert) {
this.set("foo", 1);
this.set("onLeaveChannel", () => this.set("foo", 2));
this.set("channel", {
id: 1,
chatable_type: "DirectMessage",
chatable: {
users: [{ id: 1 }],
},
});
this.foo = 1;
this.onLeaveChannel = () => (this.foo = 2);
this.channel = fabricators.directMessageChatChannel({ users: [{ id: 1 }] });
await render(
hbs`<ChatChannelLeaveBtn @channel={{this.channel}} @onLeaveChannel={{this.onLeaveChannel}} />`
@ -34,7 +29,7 @@ module("Discourse Chat | Component | chat-channel-leave-btn", function (hooks) {
});
test("has a specific title for direct message channel", async function (assert) {
this.set("channel", { chatable_type: "DirectMessage" });
this.channel = fabricators.directMessageChatChannel();
await render(hbs`<ChatChannelLeaveBtn @channel={{this.channel}} />`);
@ -43,7 +38,7 @@ module("Discourse Chat | Component | chat-channel-leave-btn", function (hooks) {
});
test("has a specific title for message channel", async function (assert) {
this.set("channel", { chatable_type: "Topic" });
this.channel = fabricators.chatChannel();
await render(hbs`<ChatChannelLeaveBtn @channel={{this.channel}} />`);
@ -53,7 +48,7 @@ module("Discourse Chat | Component | chat-channel-leave-btn", function (hooks) {
test("is not visible on mobile", async function (assert) {
this.site.mobileView = true;
this.set("channel", { chatable_type: "Topic" });
this.channel = fabricators.chatChannel();
await render(hbs`<ChatChannelLeaveBtn @channel={{this.channel}} />`);

View File

@ -29,7 +29,7 @@ module("Discourse Chat | Component | chat-channel-metadata", function (hooks) {
test("unreadIndicator", async function (assert) {
this.channel = fabricators.directMessageChatChannel();
this.channel.currentUserMembership.unread_count = 1;
this.channel.currentUserMembership.unreadCount = 1;
this.unreadIndicator = true;
await render(

View File

@ -15,10 +15,9 @@ module(
"channel",
fabricators.chatChannel({ chatable_type: "Category" })
);
this.channel.setProperties({
description: "Important stuff is announced here.",
title: "announcements",
});
this.channel.description = "Important stuff is announced here.";
this.channel.title = "announcements";
this.currentUser.set("has_chat_enabled", true);
this.siteSettings.chat_enabled = true;
});
@ -49,7 +48,7 @@ module(
});
test("no channel description", async function (assert) {
this.channel.set("description", null);
this.channel.description = null;
await render(hbs`<ChatChannelPreviewCard @channel={{this.channel}} />`);
@ -83,7 +82,7 @@ module(
});
test("closed channel", async function (assert) {
this.channel.set("status", "closed");
this.channel.status = "closed";
await render(hbs`<ChatChannelPreviewCard @channel={{this.channel}} />`);
assert.false(

View File

@ -131,7 +131,7 @@ module("Discourse Chat | Component | chat-channel-row", function (hooks) {
assert.dom(".chat-channel-row").doesNotHaveClass("has-unread");
this.categoryChatChannel.currentUserMembership.unread_count = 1;
this.categoryChatChannel.currentUserMembership.unreadCount = 1;
await render(hbs`<ChatChannelRow @channel={{this.categoryChatChannel}} />`);

View File

@ -10,12 +10,9 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) {
setupRenderingTest(hooks);
test("category channel", async function (assert) {
this.set(
"channel",
fabricators.chatChannel({
chatable_type: CHATABLE_TYPES.categoryChannel,
})
);
this.channel = fabricators.chatChannel({
chatable_type: CHATABLE_TYPES.categoryChannel,
});
await render(hbs`<ChatChannelTitle @channel={{this.channel}} />`);
@ -30,13 +27,10 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) {
});
test("category channel - escapes title", async function (assert) {
this.set(
"channel",
fabricators.chatChannel({
chatable_type: CHATABLE_TYPES.categoryChannel,
title: "<div class='xss'>evil</div>",
})
);
this.channel = fabricators.chatChannel({
chatable_type: CHATABLE_TYPES.categoryChannel,
title: "<div class='xss'>evil</div>",
});
await render(hbs`<ChatChannelTitle @channel={{this.channel}} />`);
@ -44,13 +38,10 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) {
});
test("category channel - read restricted", async function (assert) {
this.set(
"channel",
fabricators.chatChannel({
chatable_type: CHATABLE_TYPES.categoryChannel,
chatable: { read_restricted: true },
})
);
this.channel = fabricators.chatChannel({
chatable_type: CHATABLE_TYPES.categoryChannel,
chatable: { read_restricted: true },
});
await render(hbs`<ChatChannelTitle @channel={{this.channel}} />`);
@ -58,13 +49,10 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) {
});
test("category channel - not read restricted", async function (assert) {
this.set(
"channel",
fabricators.chatChannel({
chatable_type: CHATABLE_TYPES.categoryChannel,
chatable: { read_restricted: false },
})
);
this.channel = fabricators.chatChannel({
chatable_type: CHATABLE_TYPES.categoryChannel,
chatable: { read_restricted: false },
});
await render(hbs`<ChatChannelTitle @channel={{this.channel}} />`);
@ -72,7 +60,7 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) {
});
test("direct message channel - one user", async function (assert) {
this.set("channel", fabricators.directMessageChatChannel());
this.channel = fabricators.directMessageChatChannel();
await render(hbs`<ChatChannelTitle @channel={{this.channel}} />`);
@ -98,7 +86,7 @@ module("Discourse Chat | Component | chat-channel-title", function (hooks) {
avatar_template: "/letter_avatar_proxy/v3/letter/t/31188e/{size}.png",
});
this.set("channel", channel);
this.channel = channel;
await render(hbs`<ChatChannelTitle @channel={{this.channel}} />`);

View File

@ -11,8 +11,8 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
setupRenderingTest(hooks);
function generateMessageProps(messageData = {}) {
const chatChannel = ChatChannel.create({
chatable: { id: 1 },
const channel = ChatChannel.create({
chatable_id: 1,
chatable_type: "Category",
id: 9,
title: "Site",
@ -21,15 +21,15 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
unread_count: 0,
muted: false,
},
canDeleteSelf: true,
canDeleteOthers: true,
canFlag: true,
userSilenced: false,
canModerate: true,
can_delete_self: true,
can_delete_others: true,
can_flag: true,
user_silenced: false,
can_moderate: true,
});
return {
message: ChatMessage.create(
chatChannel,
channel,
Object.assign(
{
id: 178,
@ -44,7 +44,7 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
messageData
)
),
chatChannel,
channel,
afterExpand: () => {},
onHoverMessage: () => {},
messageDidEnterViewport: () => {},
@ -55,7 +55,7 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
const template = hbs`
<ChatMessage
@message={{this.message}}
@channel={{this.chatChannel}}
@channel={{this.channel}}
@messageDidEnterViewport={{this.messageDidEnterViewport}}
@messageDidLeaveViewport={{this.messageDidLeaveViewport}}
/>
@ -64,6 +64,7 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
test("Message with edits", async function (assert) {
this.setProperties(generateMessageProps({ edited: true }));
await render(template);
assert.true(
exists(".chat-message-edited"),
"has the correct edited css class"
@ -83,6 +84,7 @@ module("Discourse Chat | Component | chat-message", function (hooks) {
test("Hidden message", async function (assert) {
this.setProperties(generateMessageProps({ hidden: true }));
await render(template);
assert.true(
exists(".chat-message-hidden .chat-message-expand"),
"has the correct hidden css class and expand button within"

View File

@ -2,9 +2,9 @@ import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
import hbs from "htmlbars-inline-precompile";
import fabricators from "../helpers/fabricators";
import MockPresenceChannel from "../helpers/mock-presence-channel";
import { module, test } from "qunit";
import { render } from "@ember/test-helpers";
import { render, settled } from "@ember/test-helpers";
import { joinChannel } from "discourse/tests/helpers/presence-pretender";
module(
"Discourse Chat | Component | chat-replying-indicator",
@ -12,155 +12,148 @@ module(
setupRenderingTest(hooks);
test("not displayed when no one is replying", async function (assert) {
this.set("chatChannel", fabricators.chatChannel());
this.set(
"presenceChannel",
MockPresenceChannel.create({
name: `/chat-reply/${this.chatChannel.id}`,
})
);
this.channel = fabricators.chatChannel();
await render(
hbs`<ChatReplyingIndicator @presenceChannel={{this.presenceChannel}} @chatChannel={{this.chatChannel}} />`
);
await render(hbs`<ChatReplyingIndicator @channel={{this.channel}} />`);
assert.false(exists(".chat-replying-indicator__text"));
});
test("displays indicator when user is replying", async function (assert) {
this.set("chatChannel", fabricators.chatChannel());
this.set(
"presenceChannel",
MockPresenceChannel.create({
name: `/chat-reply/${this.chatChannel.id}`,
})
);
this.channel = fabricators.chatChannel();
await render(
hbs`<ChatReplyingIndicator @presenceChannel={{this.presenceChannel}} @chatChannel={{this.chatChannel}} />`
);
await render(hbs`<ChatReplyingIndicator @channel={{this.channel}} />`);
const sam = { id: 1, username: "sam" };
this.set("presenceChannel.users", [sam]);
await joinChannel("/chat-reply/1", {
id: 1,
avatar_template: "/images/avatar.png",
username: "sam",
});
assert.strictEqual(
query(".chat-replying-indicator__text").innerText,
`${sam.username} is typing`
`sam is typing`
);
});
test("displays indicator when 2 or 3 users are replying", async function (assert) {
this.set("chatChannel", fabricators.chatChannel());
this.set(
"presenceChannel",
MockPresenceChannel.create({
name: `/chat-reply/${this.chatChannel.id}`,
})
);
this.channel = fabricators.chatChannel();
await render(
hbs`<ChatReplyingIndicator @presenceChannel={{this.presenceChannel}} @chatChannel={{this.chatChannel}} />`
);
await render(hbs`<ChatReplyingIndicator @channel={{this.channel}} />`);
const sam = { id: 1, username: "sam" };
const mark = { id: 2, username: "mark" };
this.set("presenceChannel.users", [sam, mark]);
await joinChannel("/chat-reply/1", {
id: 1,
avatar_template: "/images/avatar.png",
username: "sam",
});
await joinChannel("/chat-reply/1", {
id: 2,
avatar_template: "/images/avatar.png",
username: "mark",
});
assert.strictEqual(
query(".chat-replying-indicator__text").innerText,
`${sam.username} and ${mark.username} are typing`
`sam and mark are typing`
);
});
test("displays indicator when 3 users are replying", async function (assert) {
this.set("chatChannel", fabricators.chatChannel());
this.set(
"presenceChannel",
MockPresenceChannel.create({
name: `/chat-reply/${this.chatChannel.id}`,
})
);
this.channel = fabricators.chatChannel();
await render(
hbs`<ChatReplyingIndicator @presenceChannel={{this.presenceChannel}} @chatChannel={{this.chatChannel}} />`
);
await render(hbs`<ChatReplyingIndicator @channel={{this.channel}} />`);
const sam = { id: 1, username: "sam" };
const mark = { id: 2, username: "mark" };
const joffrey = { id: 3, username: "joffrey" };
this.set("presenceChannel.users", [sam, mark, joffrey]);
await joinChannel("/chat-reply/1", {
id: 1,
avatar_template: "/images/avatar.png",
username: "sam",
});
await joinChannel("/chat-reply/1", {
id: 2,
avatar_template: "/images/avatar.png",
username: "mark",
});
await joinChannel("/chat-reply/1", {
id: 3,
avatar_template: "/images/avatar.png",
username: "joffrey",
});
assert.strictEqual(
query(".chat-replying-indicator__text").innerText,
`${sam.username}, ${mark.username} and ${joffrey.username} are typing`
`sam, mark and joffrey are typing`
);
});
test("displays indicator when more than 3 users are replying", async function (assert) {
this.set("chatChannel", fabricators.chatChannel());
this.set(
"presenceChannel",
MockPresenceChannel.create({
name: `/chat-reply/${this.chatChannel.id}`,
})
);
this.channel = fabricators.chatChannel();
await render(
hbs`<ChatReplyingIndicator @presenceChannel={{this.presenceChannel}} @chatChannel={{this.chatChannel}} />`
);
await render(hbs`<ChatReplyingIndicator @channel={{this.channel}} />`);
const sam = { id: 1, username: "sam" };
const mark = { id: 2, username: "mark" };
const joffrey = { id: 3, username: "joffrey" };
const taylor = { id: 4, username: "taylor" };
this.set("presenceChannel.users", [sam, mark, joffrey, taylor]);
await joinChannel("/chat-reply/1", {
id: 1,
avatar_template: "/images/avatar.png",
username: "sam",
});
await joinChannel("/chat-reply/1", {
id: 2,
avatar_template: "/images/avatar.png",
username: "mark",
});
await joinChannel("/chat-reply/1", {
id: 3,
avatar_template: "/images/avatar.png",
username: "joffrey",
});
await joinChannel("/chat-reply/1", {
id: 4,
avatar_template: "/images/avatar.png",
username: "taylor",
});
assert.strictEqual(
query(".chat-replying-indicator__text").innerText,
`${sam.username}, ${mark.username} and 2 others are typing`
`sam, mark and 2 others are typing`
);
});
test("filters current user from list of repliers", async function (assert) {
this.set("chatChannel", fabricators.chatChannel());
this.set(
"presenceChannel",
MockPresenceChannel.create({
name: `/chat-reply/${this.chatChannel.id}`,
})
);
this.channel = fabricators.chatChannel();
await render(
hbs`<ChatReplyingIndicator @presenceChannel={{this.presenceChannel}} @chatChannel={{this.chatChannel}} />`
);
await render(hbs`<ChatReplyingIndicator @channel={{this.channel}} />`);
const sam = { id: 1, username: "sam" };
this.set("presenceChannel.users", [sam, this.currentUser]);
await joinChannel("/chat-reply/1", {
id: 1,
avatar_template: "/images/avatar.png",
username: "sam",
});
await joinChannel("/chat-reply/1", this.currentUser);
assert.strictEqual(
query(".chat-replying-indicator__text").innerText,
`${sam.username} is typing`
`sam is typing`
);
});
test("resets presence when channel is draft", async function (assert) {
this.set("chatChannel", fabricators.chatChannel());
this.set(
"presenceChannel",
MockPresenceChannel.create({
name: `/chat-reply/${this.chatChannel.id}`,
subscribed: true,
})
);
this.channel = fabricators.chatChannel();
await render(
hbs`<ChatReplyingIndicator @presenceChannel={{this.presenceChannel}} @chatChannel={{this.chatChannel}} />`
);
await render(hbs`<ChatReplyingIndicator @channel={{this.channel}} />`);
assert.true(this.presenceChannel.subscribed);
assert.dom(".chat-replying-indicator.is-subscribed").exists();
this.set("chatChannel", fabricators.chatChannel({ isDraft: true }));
assert.false(this.presenceChannel.subscribed);
this.channel.isDraft = true;
await settled();
assert.dom(".chat-replying-indicator.is-subscribed").doesNotExist();
});
}
);

View File

@ -1,9 +1,9 @@
import ChatChannel from "discourse/plugins/chat/discourse/models/chat-channel";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import hbs from "htmlbars-inline-precompile";
import I18n from "I18n";
import { module, test } from "qunit";
import { render } from "@ember/test-helpers";
import fabricators from "../helpers/fabricators";
module(
"Discourse Chat | Component | chat-retention-reminder-text",
@ -11,7 +11,7 @@ module(
setupRenderingTest(hooks);
test("when setting is set on 0", async function (assert) {
this.channel = ChatChannel.create({ chatable_type: "Category" });
this.channel = fabricators.chatChannel();
this.siteSettings.chat_channel_retention_days = 0;
await render(
@ -25,7 +25,7 @@ module(
test("when channel is a public channel", async function (assert) {
const count = 10;
this.channel = ChatChannel.create({ chatable_type: "Category" });
this.channel = fabricators.chatChannel();
this.siteSettings.chat_channel_retention_days = count;
await render(
@ -39,9 +39,7 @@ module(
test("when channel is a DM channel", async function (assert) {
const count = 10;
this.channel = ChatChannel.create({
chatable_type: "DirectMessage",
});
this.channel = fabricators.directMessageChatChannel();
this.siteSettings.chat_dm_retention_days = count;
await render(

View File

@ -2,7 +2,7 @@ import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { click, fillIn, render } from "@ember/test-helpers";
import hbs from "htmlbars-inline-precompile";
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
import { createDirectMessageChannelDraft } from "discourse/plugins/chat/discourse/models/chat-channel";
import ChatChannel from "discourse/plugins/chat/discourse/models/chat-channel";
import { Promise } from "rsvp";
import fabricators from "../helpers/fabricators";
import { module, test } from "qunit";
@ -25,7 +25,7 @@ module("Discourse Chat | Component | direct-message-creator", function (hooks) {
test("search", async function (assert) {
this.set("chat", mockChat(this));
this.set("channel", createDirectMessageChannelDraft());
this.set("channel", ChatChannel.createDirectMessageChannelDraft());
await render(
hbs`<DirectMessageCreator @channel={{this.channel}} @chat={{this.chat}} />`
@ -37,7 +37,7 @@ module("Discourse Chat | Component | direct-message-creator", function (hooks) {
test("select/deselect", async function (assert) {
this.set("chat", mockChat(this));
this.set("channel", createDirectMessageChannelDraft());
this.set("channel", ChatChannel.createDirectMessageChannelDraft());
await render(
hbs`<DirectMessageCreator @channel={{this.channel}} @chat={{this.chat}} />`
@ -54,7 +54,7 @@ module("Discourse Chat | Component | direct-message-creator", function (hooks) {
test("no search results", async function (assert) {
this.set("chat", mockChat(this, { users: [] }));
this.set("channel", createDirectMessageChannelDraft());
this.set("channel", ChatChannel.createDirectMessageChannelDraft());
await render(
hbs`<DirectMessageCreator @channel={{this.channel}} @chat={{this.chat}} />`
@ -66,7 +66,7 @@ module("Discourse Chat | Component | direct-message-creator", function (hooks) {
test("loads user on first load", async function (assert) {
this.set("chat", mockChat(this));
this.set("channel", createDirectMessageChannelDraft());
this.set("channel", ChatChannel.createDirectMessageChannelDraft());
await render(
hbs`<DirectMessageCreator @channel={{this.channel}} @chat={{this.chat}} />`
@ -78,7 +78,7 @@ module("Discourse Chat | Component | direct-message-creator", function (hooks) {
test("do not load more users after selection", async function (assert) {
this.set("chat", mockChat(this));
this.set("channel", createDirectMessageChannelDraft());
this.set("channel", ChatChannel.createDirectMessageChannelDraft());
await render(
hbs`<DirectMessageCreator @channel={{this.channel}} @chat={{this.chat}} />`
@ -90,7 +90,7 @@ module("Discourse Chat | Component | direct-message-creator", function (hooks) {
test("apply is-focused to filter-area on focus input", async function (assert) {
this.set("chat", mockChat(this));
this.set("channel", createDirectMessageChannelDraft());
this.set("channel", ChatChannel.createDirectMessageChannelDraft());
await render(
hbs`<DirectMessageCreator @channel={{this.channel}} @chat={{this.chat}} /><button class="test-blur">blur</button>`
@ -105,7 +105,7 @@ module("Discourse Chat | Component | direct-message-creator", function (hooks) {
test("state is reset on channel change", async function (assert) {
this.set("chat", mockChat(this));
this.set("channel", createDirectMessageChannelDraft());
this.set("channel", ChatChannel.createDirectMessageChannelDraft());
await render(
hbs`<DirectMessageCreator @channel={{this.channel}} @chat={{this.chat}} />`
@ -115,7 +115,7 @@ module("Discourse Chat | Component | direct-message-creator", function (hooks) {
assert.strictEqual(query(".filter-usernames").value, "hawk");
this.set("channel", fabricators.chatChannel());
this.set("channel", createDirectMessageChannelDraft());
this.set("channel", ChatChannel.createDirectMessageChannelDraft());
assert.strictEqual(query(".filter-usernames").value, "");
assert.true(exists(".filter-area.is-focused"));
@ -129,7 +129,7 @@ module("Discourse Chat | Component | direct-message-creator", function (hooks) {
};
const chat = mockChat(this, { users: [userWithStatus] });
this.set("chat", chat);
this.set("channel", createDirectMessageChannelDraft());
this.set("channel", ChatChannel.createDirectMessageChannelDraft());
await render(
hbs`<DirectMessageCreator @channel={{this.channel}} @chat={{this.chat}} />`

View File

@ -138,7 +138,7 @@ export function directMessageChannelPretender(
opts = { unread_count: 0, muted: false }
) {
let copy = cloneJSON(directMessageChannels[0]);
copy.chat_channel.currentUserMembership.unread_count = opts.unread_count;
copy.chat_channel.currentUserMembership.unreadCount = opts.unread_count;
copy.chat_channel.currentUserMembership.muted = opts.muted;
server.get("/chat/chat_channels/75.json", () => helper.response(copy));
}
@ -150,13 +150,13 @@ export function chatChannelPretender(server, helper, changes = []) {
let found;
found = copy.public_channels.find((c) => c.id === change.id);
if (found) {
found.currentUserMembership.unread_count = change.unread_count;
found.currentUserMembership.unreadCount = change.unread_count;
found.currentUserMembership.muted = change.muted;
}
if (!found) {
found = copy.direct_message_channels.find((c) => c.id === change.id);
if (found) {
found.currentUserMembership.unread_count = change.unread_count;
found.currentUserMembership.unreadCount = change.unread_count;
found.currentUserMembership.muted = change.muted;
}
}

View File

@ -1,15 +0,0 @@
import EmberObject from "@ember/object";
export default class MockPresenceChannel extends EmberObject {
users = [];
name = null;
subscribed = false;
async unsubscribe() {
this.set("subscribed", false);
}
async subscribe() {
this.set("subscribed", true);
}
}

View File

@ -83,12 +83,12 @@ acceptance("Discourse Chat | Unit | Service | chat-guardian", function (needs) {
set(this.currentUser, "admin", true);
set(this.currentUser, "moderator", true);
channel.set("status", "read_only");
channel.status = "read_only";
assert.notOk(this.chatGuardian.canArchiveChannel(channel));
channel.set("status", "open");
channel.status = "open";
channel.set("status", "archived");
channel.status = "archived";
assert.notOk(this.chatGuardian.canArchiveChannel(channel));
channel.set("status", "open");
channel.status = "open";
});
});