mirror of
https://github.com/discourse/discourse.git
synced 2025-02-20 10:56:15 +08:00
FEATURE: Allow users to DM groups in chat (#25189)
Allows users to create DMs by selecting groups as a target. It also allows adding user groups to an existing chat - When creating the channel, it expands the user group and adds all its members with chat enabled to the channel. - After creation, there's no difference between adding a group or adding its members individually. - Users can add multiple groups and users simultaneously. - There are UI validations; the member count preview updates according to the member count of added groups, and it does not allow users to add more members than SiteSetting.chat_max_direct_message_users."
This commit is contained in:
parent
bd2ca8d617
commit
f4e51e0789
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Chat
|
||||
class UsersFromUsernamesAndGroupsQuery
|
||||
def self.call(usernames:, groups:, excluded_user_ids: [])
|
||||
User
|
||||
.joins(:user_option)
|
||||
.left_outer_joins(:groups)
|
||||
.where(user_options: { chat_enabled: true })
|
||||
.where(
|
||||
"username IN (?) OR (groups.name IN (?) AND group_users.user_id IS NOT NULL)",
|
||||
usernames,
|
||||
groups,
|
||||
)
|
||||
.where.not(id: excluded_user_ids)
|
||||
.distinct
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Chat
|
||||
class ChatableGroupSerializer < BasicGroupSerializer
|
||||
attributes :chat_enabled, :chat_enabled_user_count, :can_chat
|
||||
|
||||
def chat_enabled
|
||||
SiteSetting.chat_enabled
|
||||
end
|
||||
|
||||
def chat_enabled_user_count
|
||||
object.users.count { |user| user.user_option&.chat_enabled }
|
||||
end
|
||||
|
||||
def can_chat
|
||||
# + 1 for current user
|
||||
chat_enabled && chat_enabled_user_count + 1 <= SiteSetting.chat_max_direct_message_users
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,6 +3,7 @@
|
|||
module Chat
|
||||
class ChatablesSerializer < ::ApplicationSerializer
|
||||
attributes :users
|
||||
attributes :groups
|
||||
attributes :direct_message_channels
|
||||
attributes :category_channels
|
||||
|
||||
|
@ -18,6 +19,18 @@ module Chat
|
|||
.as_json
|
||||
end
|
||||
|
||||
def groups
|
||||
(object.groups || [])
|
||||
.map do |group|
|
||||
{
|
||||
identifier: "g-#{group.id}",
|
||||
model: ::Chat::ChatableGroupSerializer.new(group, scope: scope, root: false),
|
||||
type: "group",
|
||||
}
|
||||
end
|
||||
.as_json
|
||||
end
|
||||
|
||||
def direct_message_channels
|
||||
(object.direct_message_channels || [])
|
||||
.map do |channel|
|
||||
|
|
|
@ -20,6 +20,7 @@ module Chat
|
|||
# @param [Integer] id of the channel
|
||||
# @param [Hash] params_to_create
|
||||
# @option params_to_create [Array<String>] usernames
|
||||
# @option params_to_create [Array<String>] groups
|
||||
# @return [Service::Base::Context]
|
||||
contract
|
||||
model :channel
|
||||
|
@ -27,6 +28,7 @@ module Chat
|
|||
model :users, optional: true
|
||||
|
||||
transaction do
|
||||
step :validate_user_count
|
||||
step :upsert_memberships
|
||||
step :recompute_users_count
|
||||
step :notice_channel
|
||||
|
@ -35,20 +37,15 @@ module Chat
|
|||
# @!visibility private
|
||||
class Contract
|
||||
attribute :usernames, :array
|
||||
validates :usernames, presence: true
|
||||
attribute :groups, :array
|
||||
|
||||
attribute :channel_id, :integer
|
||||
validates :channel_id, presence: true
|
||||
|
||||
validate :usernames_length
|
||||
validate :target_presence
|
||||
|
||||
def usernames_length
|
||||
if usernames && usernames.length > SiteSetting.chat_max_direct_message_users + 1 # 1 for current user
|
||||
errors.add(
|
||||
:usernames,
|
||||
"should have less than #{SiteSetting.chat_max_direct_message_users} elements",
|
||||
)
|
||||
end
|
||||
def target_presence
|
||||
usernames.present? || groups.present?
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -60,17 +57,23 @@ module Chat
|
|||
end
|
||||
|
||||
def fetch_users(contract:, channel:, **)
|
||||
::User.where(
|
||||
"username IN (?) AND id NOT IN (?)",
|
||||
[*contract.usernames],
|
||||
channel.chatable.direct_message_users.select(:user_id),
|
||||
).to_a
|
||||
::Chat::UsersFromUsernamesAndGroupsQuery.call(
|
||||
usernames: contract.usernames,
|
||||
groups: contract.groups,
|
||||
excluded_user_ids: channel.chatable.direct_message_users.pluck(:user_id),
|
||||
)
|
||||
end
|
||||
|
||||
def fetch_channel(contract:, **)
|
||||
::Chat::Channel.includes(:chatable).find_by(id: contract.channel_id)
|
||||
end
|
||||
|
||||
def validate_user_count(channel:, users:, **)
|
||||
if channel.user_count + users.length > SiteSetting.chat_max_direct_message_users
|
||||
fail!("should have less than #{SiteSetting.chat_max_direct_message_users} elements")
|
||||
end
|
||||
end
|
||||
|
||||
def upsert_memberships(channel:, users:, **)
|
||||
always_level = ::Chat::UserChatChannelMembership::NOTIFICATION_LEVELS[:always]
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ module Chat
|
|||
# @param [Guardian] guardian
|
||||
# @param [Hash] params_to_create
|
||||
# @option params_to_create [Array<String>] target_usernames
|
||||
# @option params_to_create [Array<String>] target_groups
|
||||
# @return [Service::Base::Context]
|
||||
|
||||
policy :can_create_direct_message
|
||||
|
@ -32,6 +33,7 @@ module Chat
|
|||
class_name: Chat::DirectMessageChannel::CanCommunicateAllPartiesPolicy
|
||||
model :direct_message, :fetch_or_create_direct_message
|
||||
model :channel, :fetch_or_create_channel
|
||||
step :validate_user_count
|
||||
step :set_optional_name
|
||||
step :update_memberships
|
||||
step :recompute_users_count
|
||||
|
@ -40,7 +42,13 @@ module Chat
|
|||
class Contract
|
||||
attribute :name, :string
|
||||
attribute :target_usernames, :array
|
||||
validates :target_usernames, presence: true
|
||||
attribute :target_groups, :array
|
||||
|
||||
validate :target_presence
|
||||
|
||||
def target_presence
|
||||
target_usernames.present? || target_groups.present?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -50,13 +58,22 @@ module Chat
|
|||
end
|
||||
|
||||
def fetch_target_users(guardian:, contract:, **)
|
||||
User.where(username: [guardian.user.username, *contract.target_usernames]).to_a
|
||||
::Chat::UsersFromUsernamesAndGroupsQuery.call(
|
||||
usernames: [*contract.target_usernames, guardian.user.username],
|
||||
groups: contract.target_groups,
|
||||
)
|
||||
end
|
||||
|
||||
def fetch_user_comm_screener(target_users:, guardian:, **)
|
||||
UserCommScreener.new(acting_user: guardian.user, target_user_ids: target_users.map(&:id))
|
||||
end
|
||||
|
||||
def validate_user_count(target_users:, **)
|
||||
if target_users.length > SiteSetting.chat_max_direct_message_users
|
||||
fail!("should have less than #{SiteSetting.chat_max_direct_message_users} elements")
|
||||
end
|
||||
end
|
||||
|
||||
def actor_allows_dms(user_comm_screener:, **)
|
||||
!user_comm_screener.actor_disallowing_all_pms?
|
||||
end
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Chat
|
||||
# Returns a list of chatables (users, category channels, direct message channels) that can be chatted with.
|
||||
# Returns a list of chatables (users, groups ,category channels, direct message channels) that can be chatted with.
|
||||
#
|
||||
# @example
|
||||
# Chat::SearchChatable.call(term: "@bob", guardian: guardian)
|
||||
|
@ -18,6 +18,7 @@ module Chat
|
|||
step :clean_term
|
||||
model :memberships, optional: true
|
||||
model :users, optional: true
|
||||
model :groups, optional: true
|
||||
model :category_channels, optional: true
|
||||
model :direct_message_channels, optional: true
|
||||
|
||||
|
@ -25,6 +26,7 @@ module Chat
|
|||
class Contract
|
||||
attribute :term, :string, default: ""
|
||||
attribute :include_users, :boolean, default: true
|
||||
attribute :include_groups, :boolean, default: true
|
||||
attribute :include_category_channels, :boolean, default: true
|
||||
attribute :include_direct_message_channels, :boolean, default: true
|
||||
attribute :excluded_memberships_channel_id, :integer
|
||||
|
@ -46,6 +48,12 @@ module Chat
|
|||
search_users(context, guardian, contract)
|
||||
end
|
||||
|
||||
def fetch_groups(guardian:, contract:, **)
|
||||
return unless contract.include_groups
|
||||
return unless guardian.can_create_direct_message?
|
||||
search_groups(context, guardian, contract)
|
||||
end
|
||||
|
||||
def fetch_category_channels(guardian:, contract:, **)
|
||||
return unless contract.include_category_channels
|
||||
return if !SiteSetting.enable_public_channels
|
||||
|
@ -109,5 +117,15 @@ module Chat
|
|||
|
||||
user_search
|
||||
end
|
||||
|
||||
def search_groups(context, guardian, contract)
|
||||
Group
|
||||
.visible_groups(guardian.user)
|
||||
.includes(users: :user_option)
|
||||
.where(
|
||||
"groups.name ILIKE :term_like OR groups.full_name ILIKE :term_like",
|
||||
term_like: "%#{context.term}%",
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,9 +17,14 @@ export default class AddMembers extends Component {
|
|||
@service loadingSlider;
|
||||
|
||||
get membersCount() {
|
||||
return (
|
||||
this.args.members?.length + (this.args.channel?.membershipsCount ?? 0)
|
||||
);
|
||||
const userCount = this.args.members?.reduce((acc, member) => {
|
||||
if (member.type === "group") {
|
||||
return acc + member.model.chat_enabled_user_count;
|
||||
} else {
|
||||
return acc + 1;
|
||||
}
|
||||
}, 0);
|
||||
return userCount + (this.args.channel?.membershipsCount ?? 0);
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -27,10 +32,18 @@ export default class AddMembers extends Component {
|
|||
try {
|
||||
this.loadingSlider.transitionStarted();
|
||||
|
||||
await this.chatApi.addMembersToChannel(
|
||||
this.args.channel.id,
|
||||
this.args.members.mapBy("model.username")
|
||||
);
|
||||
const usernames = this.args.members
|
||||
.filter((member) => member.type === "user")
|
||||
.mapBy("model.username");
|
||||
|
||||
const groups = this.args.members
|
||||
.filter((member) => member.type === "group")
|
||||
.mapBy("model.name");
|
||||
|
||||
await this.chatApi.addMembersToChannel(this.args.channel.id, {
|
||||
usernames,
|
||||
groups,
|
||||
});
|
||||
|
||||
this.toasts.success({ data: { message: I18n.t("saved") } });
|
||||
this.router.transitionTo(
|
||||
|
@ -58,6 +71,7 @@ export default class AddMembers extends Component {
|
|||
@onChange={{@onChangeMembers}}
|
||||
@close={{@close}}
|
||||
@cancel={{@cancel}}
|
||||
@membersCount={{this.membersCount}}
|
||||
@maxReached={{gte
|
||||
this.membersCount
|
||||
this.siteSettings.chat_max_direct_message_users
|
||||
|
|
|
@ -2,6 +2,7 @@ import Component from "@glimmer/component";
|
|||
import { inject as service } from "@ember/service";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import gt from "truth-helpers/helpers/gt";
|
||||
import not from "truth-helpers/helpers/not";
|
||||
import ChannelTitle from "discourse/plugins/chat/discourse/components/channel-title";
|
||||
|
||||
export default class Channel extends Component {
|
||||
|
@ -16,7 +17,10 @@ export default class Channel extends Component {
|
|||
}
|
||||
|
||||
<template>
|
||||
<div class="chat-message-creator__chatable -category-channel">
|
||||
<div
|
||||
class="chat-message-creator__chatable -category-channel"
|
||||
data-disabled={{not @item.enabled}}
|
||||
>
|
||||
<ChannelTitle @channel={{@item.model}} />
|
||||
|
||||
{{#if (gt @item.tracking.unreadCount 0)}}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { inject as service } from "@ember/service";
|
||||
import icon from "discourse-common/helpers/d-icon";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
export default class ChatableGroup extends Component {
|
||||
@service currentUser;
|
||||
@service siteSettings;
|
||||
|
||||
group_with_too_many_members = I18n.t(
|
||||
"chat.new_message_modal.group_with_too_many_members",
|
||||
{ membersCount: this.args.item.model.chat_enabled_user_count }
|
||||
);
|
||||
|
||||
get isDisabled() {
|
||||
if (!this.args.membersCount) {
|
||||
return !this.args.item.enabled;
|
||||
}
|
||||
|
||||
return (
|
||||
this.args.membersCount + this.args.item.model.chat_enabled_user_count >
|
||||
this.siteSettings.chat_max_direct_message_users
|
||||
);
|
||||
}
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="chat-message-creator__chatable -group"
|
||||
data-disabled={{this.isDisabled}}
|
||||
>
|
||||
<div class="chat-message-creator__group-icon">
|
||||
{{icon "user-friends"}}
|
||||
</div>
|
||||
<div class="chat-message-creator__group-name">
|
||||
{{@item.model.name}}
|
||||
</div>
|
||||
|
||||
{{#if this.isDisabled}}
|
||||
<span class="chat-message-creator__chatable -disabled-chat">
|
||||
{{this.group_with_too_many_members}}
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</template>
|
||||
}
|
|
@ -20,6 +20,7 @@ export default class ChatablesLoader {
|
|||
term,
|
||||
options = {
|
||||
includeUsers: true,
|
||||
includeGroups: true,
|
||||
includeCategoryChannels: true,
|
||||
includeDirectMessageChannels: true,
|
||||
excludedUserIds: null,
|
||||
|
@ -52,6 +53,7 @@ export default class ChatablesLoader {
|
|||
|
||||
return [
|
||||
...results.users,
|
||||
...results.groups,
|
||||
...results.direct_message_channels,
|
||||
...results.category_channels,
|
||||
]
|
||||
|
@ -84,6 +86,10 @@ export default class ChatablesLoader {
|
|||
}
|
||||
|
||||
#injectTracking(chatable) {
|
||||
if (!chatable.type === "channel") {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.chatChannelsManager.allChannels.find(
|
||||
(channel) => channel.id === chatable.model.id
|
||||
)?.tracking;
|
||||
|
|
|
@ -5,8 +5,8 @@ import { action } from "@ember/object";
|
|||
import concatClass from "discourse/helpers/concat-class";
|
||||
import I18n from "discourse-i18n";
|
||||
import eq from "truth-helpers/helpers/eq";
|
||||
import not from "truth-helpers/helpers/not";
|
||||
import Channel from "./channel";
|
||||
import Group from "./group";
|
||||
import ListAction from "./list-action";
|
||||
import User from "./user";
|
||||
|
||||
|
@ -21,6 +21,8 @@ export default class List extends Component {
|
|||
return ListAction;
|
||||
case "user":
|
||||
return User;
|
||||
case "group":
|
||||
return Group;
|
||||
case "channel":
|
||||
return Channel;
|
||||
}
|
||||
|
@ -74,9 +76,12 @@ export default class List extends Component {
|
|||
tabindex="0"
|
||||
data-identifier={{item.identifier}}
|
||||
id={{item.id}}
|
||||
data-disabled={{not item.enabled}}
|
||||
>
|
||||
{{component (this.componentForItem item.type) item=item}}
|
||||
{{component
|
||||
(this.componentForItem item.type)
|
||||
membersCount=@membersCount
|
||||
item=item
|
||||
}}
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { fn } from "@ember/helper";
|
|||
import DButton from "discourse/components/d-button";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import icon from "discourse-common/helpers/d-icon";
|
||||
import eq from "truth-helpers/helpers/eq";
|
||||
import ChatUserAvatar from "discourse/plugins/chat/discourse/components/chat-user-avatar";
|
||||
|
||||
const Member = <template>
|
||||
|
@ -12,14 +13,24 @@ const Member = <template>
|
|||
}}
|
||||
@action={{fn @onSelect @member}}
|
||||
>
|
||||
<ChatUserAvatar
|
||||
@user={{@member.model}}
|
||||
@interactive={{false}}
|
||||
@showPresence={{false}}
|
||||
/>
|
||||
<span class="chat-message-creator__member-username">
|
||||
{{@member.model.username}}
|
||||
</span>
|
||||
{{#if (eq @member.type "user")}}
|
||||
<ChatUserAvatar
|
||||
@user={{@member.model}}
|
||||
@interactive={{false}}
|
||||
@showPresence={{false}}
|
||||
/>
|
||||
<span class="chat-message-creator__member-username">
|
||||
{{@member.model.username}}
|
||||
</span>
|
||||
{{else if (eq @member.type "group")}}
|
||||
<div class="chat-message-creator__group-icon">
|
||||
{{icon "user-friends"}}
|
||||
</div>
|
||||
<span class="chat-message-creator__member-group">
|
||||
{{@member.model.name}}
|
||||
</span>
|
||||
{{/if}}
|
||||
|
||||
{{icon "times"}}
|
||||
</DButton>
|
||||
</template>;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Component from "@glimmer/component";
|
||||
import concatClass from "discourse/helpers/concat-class";
|
||||
import I18n from "discourse-i18n";
|
||||
import eq from "truth-helpers/helpers/eq";
|
||||
import gte from "truth-helpers/helpers/gte";
|
||||
|
||||
export default class MembersCount extends Component {
|
||||
get countLabel() {
|
||||
|
@ -15,7 +15,7 @@ export default class MembersCount extends Component {
|
|||
<div
|
||||
class={{concatClass
|
||||
"chat-message-creator__members-count"
|
||||
(if (eq @count @max) "-reached-limit")
|
||||
(if (gte @count @max) "-reached-limit")
|
||||
}}
|
||||
>
|
||||
{{this.countLabel}}
|
||||
|
|
|
@ -47,8 +47,11 @@ export default class MembersSelector extends Component {
|
|||
return;
|
||||
}
|
||||
|
||||
const chatableMembers =
|
||||
chatable.type === "group" ? chatable.model.chat_enabled_user_count : 1;
|
||||
|
||||
if (
|
||||
this.args.members.length + (this.args.channel?.membershipsCount ?? 0) >=
|
||||
this.args.membersCount + chatableMembers >
|
||||
this.siteSettings.chat_max_direct_message_users
|
||||
) {
|
||||
return;
|
||||
|
@ -138,6 +141,7 @@ export default class MembersSelector extends Component {
|
|||
@onSelect={{this.selectChatable}}
|
||||
@onHighlight={{this.highlightChatable}}
|
||||
@maxReached={{@maxReached}}
|
||||
@membersCount={{@membersCount}}
|
||||
/>
|
||||
|
||||
</ListHandler>
|
||||
|
|
|
@ -20,14 +20,28 @@ export default class NewGroup extends Component {
|
|||
placeholder = I18n.t("chat.direct_message_creator.group_name");
|
||||
|
||||
get membersCount() {
|
||||
return this.args.members?.length;
|
||||
return this.args.members?.reduce((acc, member) => {
|
||||
if (member.type === "group") {
|
||||
return acc + member.model.chat_enabled_user_count;
|
||||
} else {
|
||||
return acc + 1;
|
||||
}
|
||||
}, 1);
|
||||
}
|
||||
|
||||
@action
|
||||
async createGroup() {
|
||||
try {
|
||||
const channel = await this.chat.upsertDmChannelForUsernames(
|
||||
this.args.members.mapBy("model.username"),
|
||||
const usernames = this.args.members
|
||||
.filter((member) => member.type === "user")
|
||||
.mapBy("model.username");
|
||||
|
||||
const groups = this.args.members
|
||||
.filter((member) => member.type === "group")
|
||||
.mapBy("model.name");
|
||||
|
||||
const channel = await this.chat.upsertDmChannel(
|
||||
{ usernames, groups },
|
||||
this.newGroupTitle
|
||||
);
|
||||
|
||||
|
@ -67,6 +81,7 @@ export default class NewGroup extends Component {
|
|||
@onChange={{@onChangeMembers}}
|
||||
@close={{@close}}
|
||||
@cancel={{@cancel}}
|
||||
@membersCount={{this.membersCount}}
|
||||
@maxReached={{gte
|
||||
this.membersCount
|
||||
this.siteSettings.chat_max_direct_message_users
|
||||
|
|
|
@ -68,6 +68,13 @@ export default class ChatMessageCreatorSearch extends Component {
|
|||
|
||||
await this.startOneToOneChannel(item.model.username);
|
||||
break;
|
||||
case "group":
|
||||
if (!item.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.args.onChangeMode(MODES.new_group, [item]);
|
||||
break;
|
||||
default:
|
||||
this.router.transitionTo("chat.channel", ...item.model.routeModels);
|
||||
this.args.close();
|
||||
|
@ -100,7 +107,24 @@ export default class ChatMessageCreatorSearch extends Component {
|
|||
|
||||
async startOneToOneChannel(username) {
|
||||
try {
|
||||
const channel = await this.chat.upsertDmChannelForUsernames([username]);
|
||||
const channel = await this.chat.upsertDmChannel({
|
||||
usernames: [username],
|
||||
});
|
||||
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.args.close?.();
|
||||
this.router.transitionTo("chat.channel", ...channel.routeModels);
|
||||
} catch (error) {
|
||||
popupAjaxError(error);
|
||||
}
|
||||
}
|
||||
|
||||
async startGroupChannel(group) {
|
||||
try {
|
||||
const channel = await this.chat.upsertDmChannel({ groups: [group] });
|
||||
|
||||
if (!channel) {
|
||||
return;
|
||||
|
|
|
@ -3,6 +3,7 @@ import { inject as service } from "@ember/service";
|
|||
import userStatus from "discourse/helpers/user-status";
|
||||
import I18n from "discourse-i18n";
|
||||
import gt from "truth-helpers/helpers/gt";
|
||||
import not from "truth-helpers/helpers/not";
|
||||
import ChatUserAvatar from "discourse/plugins/chat/discourse/components/chat-user-avatar";
|
||||
import ChatUserDisplayName from "discourse/plugins/chat/discourse/components/chat-user-display-name";
|
||||
|
||||
|
@ -12,7 +13,10 @@ export default class ChatableUser extends Component {
|
|||
disabledUserLabel = I18n.t("chat.new_message_modal.disabled_user");
|
||||
|
||||
<template>
|
||||
<div class="chat-message-creator__chatable -user">
|
||||
<div
|
||||
class="chat-message-creator__chatable -user"
|
||||
data-disabled={{not @item.enabled}}
|
||||
>
|
||||
<ChatUserAvatar @user={{@item.model}} @interactive={{false}} />
|
||||
<ChatUserDisplayName @user={{@item.model}} />
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ export default class ChatUserCardButton extends Component {
|
|||
@action
|
||||
startChatting() {
|
||||
return this.chat
|
||||
.upsertDmChannelForUsernames([this.args.user.username])
|
||||
.upsertDmChannel({ usernames: [this.args.user.username] })
|
||||
.then((channel) => {
|
||||
this.router.transitionTo("chat.channel", ...channel.routeModels);
|
||||
this.appEvents.trigger("card:close");
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { tracked } from "@glimmer/tracking";
|
||||
import { inject as service } from "@ember/service";
|
||||
import Category from "discourse/models/category";
|
||||
import Group from "discourse/models/group";
|
||||
import User from "discourse/models/user";
|
||||
import ChatChannel from "discourse/plugins/chat/discourse/models/chat-channel";
|
||||
|
||||
|
@ -17,6 +18,14 @@ export default class ChatChatable {
|
|||
});
|
||||
}
|
||||
|
||||
static createGroup(model) {
|
||||
return new ChatChatable({
|
||||
type: "group",
|
||||
model,
|
||||
identifier: `g-${model.id}`,
|
||||
});
|
||||
}
|
||||
|
||||
static createChannel(model) {
|
||||
return new ChatChatable({
|
||||
type: "channel",
|
||||
|
@ -60,6 +69,16 @@ export default class ChatChatable {
|
|||
|
||||
this.model = User.create(args.model);
|
||||
break;
|
||||
case "group":
|
||||
this.enabled = args.model.can_chat;
|
||||
|
||||
if (args.model instanceof Group) {
|
||||
this.model = args.model;
|
||||
break;
|
||||
}
|
||||
|
||||
this.model = Group.create(args.model);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,6 +86,10 @@ export default class ChatChatable {
|
|||
return this.type === "user";
|
||||
}
|
||||
|
||||
get isGroup() {
|
||||
return this.type === "group";
|
||||
}
|
||||
|
||||
get isCategory() {
|
||||
return this instanceof Category;
|
||||
}
|
||||
|
|
|
@ -552,11 +552,14 @@ export default class ChatApi extends Service {
|
|||
* Add members to a channel.
|
||||
*
|
||||
* @param {number} channelId - The ID of the channel.
|
||||
* @param {Array<string>} usernames - The usernames of the users to add.
|
||||
* @param {object} targets
|
||||
* @param {Array<string>} targets.usernames - The usernames of the users to add.
|
||||
* @param {Array<string>} targets.groups - The groups names of the groups to add.
|
||||
*/
|
||||
addMembersToChannel(channelId, usernames) {
|
||||
addMembersToChannel(channelId, targets) {
|
||||
return this.#postRequest(`/channels/${channelId}/memberships`, {
|
||||
usernames,
|
||||
usernames: targets.usernames,
|
||||
groups: targets.groups,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -390,16 +390,22 @@ export default class Chat extends Service {
|
|||
.concat(user.username)
|
||||
.uniq();
|
||||
|
||||
return this.upsertDmChannelForUsernames(usernames);
|
||||
return this.upsertDmChannel({ usernames });
|
||||
}
|
||||
|
||||
// @param {array} usernames - The usernames to create or fetch the direct message
|
||||
// channel for. The current user will automatically be included in the channel
|
||||
// when it is created.
|
||||
upsertDmChannelForUsernames(usernames, name = null) {
|
||||
// @param {object} targets - The targets to create or fetch the direct message
|
||||
// channel for. The current user will automatically be included in the channel when it is created.
|
||||
// @param {array} [targets.usernames] - The usernames to include in the direct message channel.
|
||||
// @param {array} [targets.groups] - The groups to include in the direct message channel.
|
||||
// @param {string|null} [name=null] - Optional name for the direct message channel.
|
||||
upsertDmChannel(targets, name = null) {
|
||||
return ajax("/chat/api/direct-message-channels.json", {
|
||||
method: "POST",
|
||||
data: { target_usernames: usernames.uniq(), name },
|
||||
data: {
|
||||
target_usernames: targets.usernames?.uniq(),
|
||||
target_groups: targets.groups?.uniq(),
|
||||
name,
|
||||
},
|
||||
})
|
||||
.then((response) => {
|
||||
const channel = this.chatChannelsManager.store(response.channel);
|
||||
|
|
|
@ -38,15 +38,27 @@
|
|||
color: var(--primary);
|
||||
border-color: var(--tertiary);
|
||||
|
||||
.d-icon {
|
||||
.d-icon-times {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.d-icon {
|
||||
.d-icon-times {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.chat-message-creator__group-icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
|
||||
.d-icon.d-icon-user-friends {
|
||||
color: var(--primary-high);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-message-creator__add-members__close-btn {
|
||||
|
@ -144,6 +156,7 @@
|
|||
.chat-message-creator__members-count {
|
||||
white-space: nowrap;
|
||||
color: var(--primary-medium);
|
||||
|
||||
&.-reached-limit {
|
||||
color: var(--danger);
|
||||
}
|
||||
|
@ -168,6 +181,11 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
[data-disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&-item {
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
@ -175,11 +193,6 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&[data-disabled] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.d-icon-users {
|
||||
padding: 4px 4px;
|
||||
box-sizing: border-box;
|
||||
|
@ -200,28 +213,40 @@
|
|||
}
|
||||
}
|
||||
|
||||
.chat-message-creator__chatable.-user {
|
||||
.chat-user-display-name {
|
||||
.chat-message-creator__chatable {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
&.-user .chat-user-display-name {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.chat-message-creator__chatable.-disabled-chat {
|
||||
padding-left: 0.25rem;
|
||||
}
|
||||
}
|
||||
&.-group {
|
||||
.chat-message-creator__group-name {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.chat-message-creator__chatable.-category-channel {
|
||||
.chat-channel-title__category-badge {
|
||||
.chat-message-creator__group-icon .d-icon-user-friends {
|
||||
padding: 5px 5px;
|
||||
box-sizing: border-box;
|
||||
color: var(--primary-high);
|
||||
background: var(--primary-low);
|
||||
border-radius: 100%;
|
||||
width: 24px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
&.-category-channel .chat-channel-title__category-badge {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-message-creator__chatable.-user,
|
||||
.chat-message-creator__chatable.-category-channel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&.-disabled-chat {
|
||||
padding-left: 0.25rem;
|
||||
}
|
||||
|
||||
.unread-indicator {
|
||||
margin-left: 0.5rem;
|
||||
|
@ -275,6 +300,7 @@
|
|||
transform: scale(0.1);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
|
|
|
@ -350,6 +350,7 @@ en:
|
|||
filter: "Filter"
|
||||
cant_add_more_members: "Maximum number of members reached"
|
||||
create_new_group_chat: "Create group chat"
|
||||
group_with_too_many_members: "has too many members (%{membersCount})"
|
||||
|
||||
channel_edit_name_slug_modal:
|
||||
title: Edit channel
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
describe Chat::UsersFromUsernamesAndGroupsQuery do
|
||||
fab!(:user1) { Fabricate(:user) }
|
||||
fab!(:user2) { Fabricate(:user) }
|
||||
fab!(:user3) { Fabricate(:user) }
|
||||
fab!(:user4) { Fabricate(:user) }
|
||||
fab!(:group1) { Fabricate(:public_group, users: [user1, user2]) }
|
||||
fab!(:group2) { Fabricate(:public_group, users: [user3]) }
|
||||
|
||||
context "when searching by usernames" do
|
||||
it "returns users matching the usernames" do
|
||||
result = described_class.call(usernames: [user1.username, user4.username], groups: [])
|
||||
expect(result).to contain_exactly(user1, user4)
|
||||
end
|
||||
end
|
||||
|
||||
context "when searching by groups" do
|
||||
it "returns users belonging to the specified groups" do
|
||||
result = described_class.call(usernames: [], groups: [group1.name])
|
||||
expect(result).to contain_exactly(user1, user2)
|
||||
end
|
||||
end
|
||||
|
||||
context "when searching by both usernames and groups" do
|
||||
it "returns a unique set of users matching either condition" do
|
||||
result = described_class.call(usernames: [user2.username], groups: [group2.name])
|
||||
expect(result).to contain_exactly(user2, user3)
|
||||
end
|
||||
end
|
||||
|
||||
context "when no usernames or groups are provided" do
|
||||
it "returns an empty array" do
|
||||
result = described_class.call(usernames: [], groups: [])
|
||||
expect(result).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context "when user chat is disabled" do
|
||||
before { user1.user_option.update!(chat_enabled: false) }
|
||||
|
||||
it "does not return users with chat disabled" do
|
||||
result = described_class.call(usernames: [], groups: [group1.name])
|
||||
expect(result).not_to include(user1)
|
||||
expect(result).to include(user2)
|
||||
end
|
||||
end
|
||||
|
||||
context "when excluding specific user IDs" do
|
||||
it "does not return users with specified IDs" do
|
||||
result =
|
||||
described_class.call(
|
||||
usernames: [user4.username],
|
||||
groups: [group1.name, group2.name],
|
||||
excluded_user_ids: [user1.id, user3.id],
|
||||
)
|
||||
expect(result).not_to include(user1, user3)
|
||||
expect(result).to include(user2, user4)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,7 +5,8 @@ RSpec.describe Chat::AddUsersToChannel do
|
|||
subject(:contract) { described_class.new(usernames: [], channel_id: nil) }
|
||||
|
||||
it { is_expected.to validate_presence_of :channel_id }
|
||||
it { is_expected.to validate_presence_of :usernames }
|
||||
it { is_expected.to validate_presence_of :usernames if :groups.blank? }
|
||||
it { is_expected.to validate_presence_of :groups if :usernames.blank? }
|
||||
end
|
||||
|
||||
describe ".call" do
|
||||
|
@ -15,6 +16,9 @@ RSpec.describe Chat::AddUsersToChannel do
|
|||
fab!(:users) { Fabricate.times(5, :user) }
|
||||
fab!(:direct_message) { Fabricate(:direct_message, users: [current_user], group: true) }
|
||||
fab!(:channel) { Fabricate(:direct_message_channel, chatable: direct_message) }
|
||||
fab!(:group_user_1) { Fabricate(:user) }
|
||||
fab!(:group_user_2) { Fabricate(:user) }
|
||||
fab!(:group) { Fabricate(:public_group, users: [group_user_1, group_user_2]) }
|
||||
|
||||
let(:guardian) { Guardian.new(current_user) }
|
||||
let(:params) do
|
||||
|
@ -28,6 +32,30 @@ RSpec.describe Chat::AddUsersToChannel do
|
|||
expect(result.users.map(&:username)).to contain_exactly(*users.map(&:username))
|
||||
end
|
||||
|
||||
it "includes users from groups" do
|
||||
params.merge!(groups: [group.name])
|
||||
expect(result.users.map(&:username)).to include(
|
||||
group_user_1.username,
|
||||
group_user_2.username,
|
||||
)
|
||||
end
|
||||
|
||||
context "with user count validation" do
|
||||
before { SiteSetting.chat_max_direct_message_users = 8 }
|
||||
|
||||
it "succeeds when usernames does not exceed limit" do
|
||||
expect { result }.to change { Chat::UserChatChannelMembership.count }.by(6)
|
||||
expect(result).to be_a_success
|
||||
end
|
||||
|
||||
it "succeeds when usernames and groups does not exceed limit" do
|
||||
params.merge!(groups: [group.name])
|
||||
|
||||
expect { result }.to change { Chat::UserChatChannelMembership.count }.by(8)
|
||||
expect(result).to be_a_success
|
||||
end
|
||||
end
|
||||
|
||||
it "doesn't include existing direct message users" do
|
||||
Chat::DirectMessageUser.create!(user: users.first, direct_message: direct_message)
|
||||
|
||||
|
@ -65,10 +93,10 @@ RSpec.describe Chat::AddUsersToChannel do
|
|||
end
|
||||
end
|
||||
|
||||
context "when there are too many usernames" do
|
||||
before { SiteSetting.chat_max_direct_message_users = 2 }
|
||||
context "when usernames exceeds chat_max_direct_message_users" do
|
||||
before { SiteSetting.chat_max_direct_message_users = 4 }
|
||||
|
||||
it { is_expected.to fail_a_contract }
|
||||
it { is_expected.to fail_a_step(:validate_user_count) }
|
||||
end
|
||||
|
||||
context "when channel is not found" do
|
||||
|
|
|
@ -6,7 +6,8 @@ RSpec.describe Chat::CreateDirectMessageChannel do
|
|||
|
||||
let(:params) { { target_usernames: %w[lechuck elaine] } }
|
||||
|
||||
it { is_expected.to validate_presence_of :target_usernames }
|
||||
it { is_expected.to validate_presence_of :target_usernames if :target_groups.blank? }
|
||||
it { is_expected.to validate_presence_of :target_groups if :target_usernames.blank? }
|
||||
|
||||
context "when the target_usernames argument is a string" do
|
||||
let(:params) { { target_usernames: "lechuck,elaine" } }
|
||||
|
@ -16,6 +17,14 @@ RSpec.describe Chat::CreateDirectMessageChannel do
|
|||
expect(contract.target_usernames).to eq(%w[lechuck elaine])
|
||||
end
|
||||
end
|
||||
context "when the target_groups argument is a string" do
|
||||
let(:params) { { target_groups: "admins,moderators" } }
|
||||
|
||||
it "splits it into an array" do
|
||||
contract.validate
|
||||
expect(contract.target_groups).to eq(%w[admins moderators])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe ".call" do
|
||||
|
@ -24,6 +33,8 @@ RSpec.describe Chat::CreateDirectMessageChannel do
|
|||
fab!(:current_user) { Fabricate(:user, username: "guybrush") }
|
||||
fab!(:user_1) { Fabricate(:user, username: "lechuck") }
|
||||
fab!(:user_2) { Fabricate(:user, username: "elaine") }
|
||||
fab!(:user_3) { Fabricate(:user) }
|
||||
fab!(:group) { Fabricate(:public_group, users: [user_3]) }
|
||||
|
||||
let(:guardian) { Guardian.new(current_user) }
|
||||
let(:target_usernames) { [user_1.username, user_2.username] }
|
||||
|
@ -63,6 +74,40 @@ RSpec.describe Chat::CreateDirectMessageChannel do
|
|||
end
|
||||
end
|
||||
|
||||
it "includes users from target groups" do
|
||||
params.delete(:target_usernames)
|
||||
params.merge!(target_groups: [group.name])
|
||||
|
||||
expect(result.channel.user_chat_channel_memberships.pluck(:user_id)).to include(user_3.id)
|
||||
end
|
||||
|
||||
it "combines target_usernames and target_groups" do
|
||||
params.merge!(target_groups: [group.name])
|
||||
|
||||
expect(result.channel.user_chat_channel_memberships.pluck(:user_id)).to contain_exactly(
|
||||
current_user.id,
|
||||
user_1.id,
|
||||
user_2.id,
|
||||
user_3.id,
|
||||
)
|
||||
end
|
||||
|
||||
context "with user count validation" do
|
||||
before { SiteSetting.chat_max_direct_message_users = 4 }
|
||||
|
||||
it "succeeds when target_usernames does not exceed limit" do
|
||||
expect { result }.to change { Chat::UserChatChannelMembership.count }.by(3)
|
||||
expect(result).to be_a_success
|
||||
end
|
||||
|
||||
it "succeeds when target_usernames and target_groups does not exceed limit" do
|
||||
params.merge!(target_groups: [group.name])
|
||||
|
||||
expect { result }.to change { Chat::UserChatChannelMembership.count }.by(4)
|
||||
expect(result).to be_a_success
|
||||
end
|
||||
end
|
||||
|
||||
context "when there is an existing direct message channel for the target users" do
|
||||
context "when a name has been given" do
|
||||
let(:target_usernames) { [user_1.username] }
|
||||
|
@ -121,6 +166,12 @@ RSpec.describe Chat::CreateDirectMessageChannel do
|
|||
end
|
||||
end
|
||||
|
||||
context "when target_usernames exceeds chat_max_direct_message_users" do
|
||||
before { SiteSetting.chat_max_direct_message_users = 2 }
|
||||
|
||||
it { is_expected.to fail_a_step(:validate_user_count) }
|
||||
end
|
||||
|
||||
context "when the current user cannot make direct messages" do
|
||||
fab!(:current_user) { Fabricate(:user) }
|
||||
|
||||
|
@ -129,18 +180,6 @@ RSpec.describe Chat::CreateDirectMessageChannel do
|
|||
it { is_expected.to fail_a_policy(:can_create_direct_message) }
|
||||
end
|
||||
|
||||
context "when the number of target users exceeds chat_max_direct_message_users" do
|
||||
before { SiteSetting.chat_max_direct_message_users = 1 }
|
||||
|
||||
it { is_expected.to fail_a_policy(:satisfies_dms_max_users_limit) }
|
||||
|
||||
context "when the user is staff" do
|
||||
fab!(:current_user) { Fabricate(:admin) }
|
||||
|
||||
it { is_expected.not_to fail_a_policy(:satisfies_dms_max_users_limit) }
|
||||
end
|
||||
end
|
||||
|
||||
context "when the actor is not allowing anyone to message them" do
|
||||
before { current_user.user_option.update!(allow_private_messages: false) }
|
||||
|
||||
|
|
|
@ -8,6 +8,8 @@ RSpec.describe Chat::SearchChatable do
|
|||
fab!(:sam) { Fabricate(:user, username: "sam-user") }
|
||||
fab!(:charlie) { Fabricate(:user, username: "charlie-user") }
|
||||
fab!(:alain) { Fabricate(:user, username: "alain-user") }
|
||||
fab!(:group_1) { Fabricate(:group, name: "awesome-group") }
|
||||
fab!(:group_2) { Fabricate(:group) }
|
||||
fab!(:channel_1) { Fabricate(:chat_channel, name: "bob-channel") }
|
||||
fab!(:channel_2) { Fabricate(:direct_message_channel, users: [current_user, sam]) }
|
||||
fab!(:channel_3) { Fabricate(:direct_message_channel, users: [current_user, sam, charlie]) }
|
||||
|
@ -17,6 +19,7 @@ RSpec.describe Chat::SearchChatable do
|
|||
let(:guardian) { Guardian.new(current_user) }
|
||||
let(:term) { "" }
|
||||
let(:include_users) { false }
|
||||
let(:include_groups) { false }
|
||||
let(:include_category_channels) { false }
|
||||
let(:include_direct_message_channels) { false }
|
||||
let(:excluded_memberships_channel_id) { nil }
|
||||
|
@ -25,6 +28,7 @@ RSpec.describe Chat::SearchChatable do
|
|||
guardian: guardian,
|
||||
term: term,
|
||||
include_users: include_users,
|
||||
include_groups: include_groups,
|
||||
include_category_channels: include_category_channels,
|
||||
include_direct_message_channels: include_direct_message_channels,
|
||||
excluded_memberships_channel_id: excluded_memberships_channel_id,
|
||||
|
@ -88,6 +92,32 @@ RSpec.describe Chat::SearchChatable do
|
|||
end
|
||||
end
|
||||
|
||||
context "when including groups" do
|
||||
let(:include_groups) { true }
|
||||
|
||||
it "fetches groups" do
|
||||
expect(result.groups).to include(group_1, group_2)
|
||||
end
|
||||
|
||||
it "can filter groups by name" do
|
||||
params[:term] = "awesome-group"
|
||||
expect(result.groups).to contain_exactly(group_1)
|
||||
end
|
||||
|
||||
it "excludes groups not matching the search term" do
|
||||
params[:term] = "nonexistent"
|
||||
expect(result.groups).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context "when not including groups" do
|
||||
let(:include_groups) { false }
|
||||
|
||||
it "doesn’t fetch groups" do
|
||||
expect(result.groups).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "when including category channels" do
|
||||
let(:include_category_channels) { true }
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ RSpec.describe "Flag message", type: :system do
|
|||
fab!(:current_user) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
SiteSetting.chat_max_direct_message_users = 3
|
||||
chat_system_bootstrap
|
||||
sign_in(current_user)
|
||||
end
|
||||
|
@ -87,4 +88,42 @@ RSpec.describe "Flag message", type: :system do
|
|||
|
||||
expect(page).to have_current_path(%r{/chat/c/cats/\d+})
|
||||
end
|
||||
|
||||
it "can create a new group by clicking on an user group" do
|
||||
user_1 = Fabricate(:user)
|
||||
user_2 = Fabricate(:user)
|
||||
group = Fabricate(:public_group, users: [user_1, user_2])
|
||||
|
||||
visit("/")
|
||||
chat_page.prefers_full_page
|
||||
chat_page.open_new_message
|
||||
chat_page.find(".chat-message-creator__search-input__input").fill_in(with: group.name)
|
||||
chat_page.message_creator.click_row(group)
|
||||
chat_page.find(".chat-message-creator__new-group-header__input").fill_in(with: "dogs")
|
||||
chat_page.find(".create-chat-group").click
|
||||
|
||||
expect(page).to have_current_path(%r{/chat/c/dogs/\d+})
|
||||
end
|
||||
|
||||
it "doesn’t allow adding a user group if it will exceed the member limit" do
|
||||
user_1 = Fabricate(:user)
|
||||
user_2 = Fabricate(:user)
|
||||
user_3 = Fabricate(:user)
|
||||
group = Fabricate(:public_group, users: [user_1, user_2])
|
||||
|
||||
visit("/")
|
||||
chat_page.prefers_full_page
|
||||
chat_page.open_new_message
|
||||
chat_page.find("#new-group-chat").click
|
||||
chat_page.find(".chat-message-creator__new-group-header__input").fill_in(with: "hamsters")
|
||||
chat_page.find(".chat-message-creator__members-input").fill_in(with: user_3.username)
|
||||
chat_page.message_creator.click_row(user_3)
|
||||
chat_page.find(".chat-message-creator__members-input").fill_in(with: group.name)
|
||||
chat_page.message_creator.click_row(group)
|
||||
|
||||
expect(chat_page.message_creator).to have_css("div[data-disabled]")
|
||||
expect(chat_page.message_creator).to be_listing(group)
|
||||
chat_page.message_creator.click_row(group)
|
||||
expect(chat_page.message_creator).to be_listing(group)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -129,6 +129,8 @@ module PageObjects
|
|||
selector += "[data-identifier='c-#{chatable.id}']"
|
||||
elsif chatable.try(:direct_message_channel?)
|
||||
selector += "[data-identifier='c-#{chatable.id}']"
|
||||
elsif chatable.is_a?(Group)
|
||||
selector += "[data-identifier='g-#{chatable.id}']"
|
||||
else
|
||||
selector += "[data-identifier='u-#{chatable.id}']"
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue
Block a user