FEATURE: Enforce mention limits for chat messages (#19034)
* FEATURE: Enforce mention limits for chat messages The first part of these changes adds a new setting called `max_mentions_per_chat_message`, which skips notifications when the message contains too many mentions. It also respects the `max_users_notified_per_group_mention` setting and skips notifications if expanding a group mention would exceed it. We also include a new component to display JIT warning for these limits to the user while composing a message. * Simplify ignoring/muting filter in chat_notifier * Post-send warnings for unsent warnings * Improve pluralization * Address review feedback * Fix test * Address second feedback round * Third round of feedback Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
This commit is contained in:
@@ -21,12 +21,15 @@ import { Promise } from "rsvp";
|
||||
import { translations } from "pretty-text/emoji/data";
|
||||
import { channelStatusName } from "discourse/plugins/chat/discourse/models/chat-channel";
|
||||
import { setupHashtagAutocomplete } from "discourse/lib/hashtag-autocomplete";
|
||||
import discourseDebounce from "discourse-common/lib/debounce";
|
||||
import {
|
||||
chatComposerButtons,
|
||||
chatComposerButtonsDependentKeys,
|
||||
} from "discourse/plugins/chat/discourse/lib/chat-composer-buttons";
|
||||
import { mentionRegex } from "pretty-text/mentions";
|
||||
|
||||
const THROTTLE_MS = 150;
|
||||
const MENTION_DEBOUNCE_MS = 1000;
|
||||
|
||||
export default Component.extend(TextareaTextManipulation, {
|
||||
chatChannel: null,
|
||||
@@ -41,12 +44,14 @@ export default Component.extend(TextareaTextManipulation, {
|
||||
editingMessage: null,
|
||||
onValueChange: null,
|
||||
timer: null,
|
||||
mentionsTimer: null,
|
||||
value: "",
|
||||
inProgressUploads: null,
|
||||
composerEventPrefix: "chat",
|
||||
composerFocusSelector: ".chat-composer-input",
|
||||
canAttachUploads: reads("siteSettings.chat_allow_uploads"),
|
||||
isNetworkUnreliable: reads("chat.isNetworkUnreliable"),
|
||||
typingMention: false,
|
||||
|
||||
@discourseComputed(...chatComposerButtonsDependentKeys())
|
||||
inlineButtons() {
|
||||
@@ -144,10 +149,8 @@ export default Component.extend(TextareaTextManipulation, {
|
||||
"_inProgressUploadsChanged"
|
||||
);
|
||||
|
||||
if (this.timer) {
|
||||
cancel(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
cancel(this.timer);
|
||||
cancel(this.mentionsTimer);
|
||||
|
||||
this.appEvents.off("chat:focus-composer", this, "_focusTextArea");
|
||||
this.appEvents.off("chat:insert-text", this, "insertText");
|
||||
@@ -230,6 +233,7 @@ export default Component.extend(TextareaTextManipulation, {
|
||||
replyToMsg: this.draft.replyToMsg,
|
||||
});
|
||||
|
||||
this._debouncedCaptureMentions();
|
||||
this._syncUploads(this.draft.uploads);
|
||||
this.setInReplyToMsg(this.draft.replyToMsg);
|
||||
}
|
||||
@@ -294,6 +298,13 @@ export default Component.extend(TextareaTextManipulation, {
|
||||
this.set("value", value);
|
||||
this.resizeTextarea();
|
||||
|
||||
this.typingMention = value.slice(-1) === "@";
|
||||
|
||||
if (this.typingMention && value.slice(-1) === " ") {
|
||||
this.typingMention = false;
|
||||
this._debouncedCaptureMentions();
|
||||
}
|
||||
|
||||
// throttle, not debounce, because we do eventually want to react during the typing
|
||||
this.timer = throttle(this, this._handleTextareaInput, THROTTLE_MS);
|
||||
},
|
||||
@@ -304,6 +315,44 @@ export default Component.extend(TextareaTextManipulation, {
|
||||
this.onValueChange?.(this.value, this._uploads, this.replyToMsg);
|
||||
},
|
||||
|
||||
@bind
|
||||
_debouncedCaptureMentions() {
|
||||
this.mentionsTimer = discourseDebounce(
|
||||
this,
|
||||
this._captureMentions,
|
||||
MENTION_DEBOUNCE_MS
|
||||
);
|
||||
},
|
||||
|
||||
@bind
|
||||
_captureMentions() {
|
||||
if (this.siteSettings.enable_mentions) {
|
||||
const mentions = this._extractMentions();
|
||||
this.onMentionUpdates(mentions);
|
||||
}
|
||||
},
|
||||
|
||||
_extractMentions() {
|
||||
let message = this.value;
|
||||
const regex = mentionRegex(this.siteSettings.unicode_usernames);
|
||||
const mentions = [];
|
||||
let mentionsLeft = true;
|
||||
|
||||
while (mentionsLeft) {
|
||||
const matches = message.match(regex);
|
||||
|
||||
if (matches) {
|
||||
const mention = matches[1] || matches[2];
|
||||
mentions.push(mention);
|
||||
message = message.replaceAll(`${mention}`, "");
|
||||
} else {
|
||||
mentionsLeft = false;
|
||||
}
|
||||
}
|
||||
|
||||
return mentions;
|
||||
},
|
||||
|
||||
@bind
|
||||
_blurInput() {
|
||||
document.activeElement?.blur();
|
||||
@@ -350,6 +399,7 @@ export default Component.extend(TextareaTextManipulation, {
|
||||
afterComplete: (text) => {
|
||||
this.set("value", text);
|
||||
this._focusTextArea();
|
||||
this._debouncedCaptureMentions();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -660,6 +710,7 @@ export default Component.extend(TextareaTextManipulation, {
|
||||
value: "",
|
||||
inReplyMsg: null,
|
||||
});
|
||||
this.onMentionUpdates([]);
|
||||
this._syncUploads([]);
|
||||
this._focusTextArea({ ensureAtEnd: true, resizeTextarea: true });
|
||||
this.onValueChange?.(this.value, this._uploads, this.replyToMsg);
|
||||
|
||||
@@ -27,6 +27,13 @@
|
||||
|
||||
<ChatRetentionReminder @chatChannel={{this.chatChannel}} />
|
||||
|
||||
<ChatMentionWarnings
|
||||
@unreachableGroupMentions={{this.unreachableGroupMentions}}
|
||||
@overMembersLimitGroupMentions={{this.overMembersLimitGroupMentions}}
|
||||
@tooManyMentions={{this.tooManyMentions}}
|
||||
@mentionsCount={{this.mentionsCount}}
|
||||
/>
|
||||
|
||||
<div class="chat-message-actions-mobile-anchor"></div>
|
||||
<div
|
||||
class={{concat-class
|
||||
@@ -90,7 +97,22 @@
|
||||
<ChatSelectionManager @selectedMessageIds={{this.selectedMessageIds}} @chatChannel={{this.chatChannel}} @canModerate={{this.details.can_moderate}} @cancelSelecting={{action "cancelSelecting"}} />
|
||||
{{else}}
|
||||
{{#if (or this.chatChannel.isDraft this.chatChannel.isFollowing)}}
|
||||
<ChatComposer @draft={{this.draft}} @details={{this.details}} @canInteractWithChat={{this.canInteractWithChat}} @sendMessage={{action "sendMessage"}} @editMessage={{action "editMessage"}} @setReplyTo={{action "setReplyTo"}} @loading={{this.sendingLoading}} @editingMessage={{readonly this.editingMessage}} @onCancelEditing={{this.cancelEditing}} @setInReplyToMsg={{this.setInReplyToMsg}} @onEditLastMessageRequested={{this.editLastMessageRequested}} @onValueChange={{action "composerValueChanged"}} @chatChannel={{this.chatChannel}} />
|
||||
<ChatComposer
|
||||
@draft={{this.draft}}
|
||||
@details={{this.details}}
|
||||
@canInteractWithChat={{this.canInteractWithChat}}
|
||||
@sendMessage={{action "sendMessage"}}
|
||||
@editMessage={{action "editMessage"}}
|
||||
@setReplyTo={{action "setReplyTo"}}
|
||||
@loading={{this.sendingLoading}}
|
||||
@editingMessage={{readonly this.editingMessage}}
|
||||
@onCancelEditing={{this.cancelEditing}}
|
||||
@setInReplyToMsg={{this.setInReplyToMsg}}
|
||||
@onEditLastMessageRequested={{this.editLastMessageRequested}}
|
||||
@onValueChange={{action "composerValueChanged"}}
|
||||
@chatChannel={{this.chatChannel}}
|
||||
@onMentionUpdates={{this.updateMentions}}
|
||||
/>
|
||||
{{else}}
|
||||
<ChatChannelPreviewCard @channel={{this.chatChannel}} />
|
||||
{{/if}}
|
||||
|
||||
@@ -38,6 +38,12 @@ const FETCH_MORE_MESSAGES_THROTTLE_MS = isTesting() ? 0 : 500;
|
||||
const PAST = "past";
|
||||
const FUTURE = "future";
|
||||
|
||||
const MENTION_RESULT = {
|
||||
invalid: -1,
|
||||
unreachable: 0,
|
||||
over_members_limit: 1,
|
||||
};
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: [":chat-live-pane", "sendingLoading", "loading"],
|
||||
chatChannel: null,
|
||||
@@ -68,6 +74,14 @@ export default Component.extend({
|
||||
targetMessageId: null,
|
||||
hasNewMessages: null,
|
||||
|
||||
// Track mention hints to display warnings
|
||||
unreachableGroupMentions: null, // Array
|
||||
overMembersLimitGroupMentions: null, // Array
|
||||
tooManyMentions: false,
|
||||
mentionsCount: null,
|
||||
// Complimentary structure to avoid repeating mention checks.
|
||||
_mentionWarningsSeen: null, // Hash
|
||||
|
||||
chat: service(),
|
||||
router: service(),
|
||||
chatEmojiPickerManager: service(),
|
||||
@@ -82,6 +96,9 @@ export default Component.extend({
|
||||
this._super(...arguments);
|
||||
|
||||
this.set("messages", []);
|
||||
this.set("_mentionWarningsSeen", {});
|
||||
this.set("unreachableGroupMentions", []);
|
||||
this.set("overMembersLimitGroupMentions", []);
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
@@ -1313,6 +1330,81 @@ export default Component.extend({
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
updateMentions(mentions) {
|
||||
const mentionsCount = mentions?.length;
|
||||
this.set("mentionsCount", mentionsCount);
|
||||
|
||||
if (mentionsCount > 0) {
|
||||
if (mentionsCount > this.siteSettings.max_mentions_per_chat_message) {
|
||||
this.set("tooManyMentions", true);
|
||||
} else {
|
||||
this.set("tooManyMentions", false);
|
||||
const newMentions = mentions.filter(
|
||||
(mention) => !(mention in this._mentionWarningsSeen)
|
||||
);
|
||||
|
||||
if (newMentions?.length > 0) {
|
||||
this._recordNewWarnings(newMentions, mentions);
|
||||
} else {
|
||||
this._rebuildWarnings(mentions);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.set("tooManyMentions", false);
|
||||
this.set("unreachableGroupMentions", []);
|
||||
this.set("overMembersLimitGroupMentions", []);
|
||||
}
|
||||
},
|
||||
|
||||
_recordNewWarnings(newMentions, mentions) {
|
||||
ajax("/chat/api/mentions/groups.json", {
|
||||
data: { mentions: newMentions },
|
||||
})
|
||||
.then((newWarnings) => {
|
||||
newWarnings.unreachable.forEach((warning) => {
|
||||
this._mentionWarningsSeen[warning] = MENTION_RESULT["unreachable"];
|
||||
});
|
||||
|
||||
newWarnings.over_members_limit.forEach((warning) => {
|
||||
this._mentionWarningsSeen[warning] =
|
||||
MENTION_RESULT["over_members_limit"];
|
||||
});
|
||||
|
||||
newWarnings.invalid.forEach((warning) => {
|
||||
this._mentionWarningsSeen[warning] = MENTION_RESULT["invalid"];
|
||||
});
|
||||
|
||||
this._rebuildWarnings(mentions);
|
||||
})
|
||||
.catch(this._rebuildWarnings(mentions));
|
||||
},
|
||||
|
||||
_rebuildWarnings(mentions) {
|
||||
const newWarnings = mentions.reduce(
|
||||
(memo, mention) => {
|
||||
if (
|
||||
mention in this._mentionWarningsSeen &&
|
||||
!(this._mentionWarningsSeen[mention] === MENTION_RESULT["invalid"])
|
||||
) {
|
||||
if (
|
||||
this._mentionWarningsSeen[mention] === MENTION_RESULT["unreachable"]
|
||||
) {
|
||||
memo[0].push(mention);
|
||||
} else {
|
||||
memo[1].push(mention);
|
||||
}
|
||||
}
|
||||
|
||||
return memo;
|
||||
},
|
||||
[[], []]
|
||||
);
|
||||
|
||||
this.set("unreachableGroupMentions", newWarnings[0]);
|
||||
this.set("overMembersLimitGroupMentions", newWarnings[1]);
|
||||
},
|
||||
|
||||
@action
|
||||
reStickScrollIfNeeded() {
|
||||
if (this.stickyScroll) {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{{#if this.show}}
|
||||
<div class="chat-mention-warnings">
|
||||
<div class="chat-mention-warning__icon">
|
||||
{{d-icon "exclamation-triangle"}}
|
||||
</div>
|
||||
<div class="chat-mention-warning__text">
|
||||
<div class="chat-mention-warning__header">
|
||||
{{this.warningHeaderText}}
|
||||
</div>
|
||||
<ul class={{this.listStyleClass}}>
|
||||
{{#if @tooManyMentions}}
|
||||
<li>{{this.tooManyMentionsBody}}</li>
|
||||
{{else}}
|
||||
{{#if @unreachableGroupMentions}}
|
||||
<li>{{this.unreachableBody}}</li>
|
||||
{{/if}}
|
||||
{{#if @overMembersLimitGroupMentions}}
|
||||
<li>{{this.overMembersLimitBody}}</li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
@@ -0,0 +1,163 @@
|
||||
import Component from "@glimmer/component";
|
||||
import I18n from "I18n";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class ChatMentionWarnings extends Component {
|
||||
@service siteSettings;
|
||||
@service currentUser;
|
||||
|
||||
get unreachableGroupMentionsCount() {
|
||||
return this.args?.unreachableGroupMentions.length;
|
||||
}
|
||||
|
||||
get overMembersLimitMentionsCount() {
|
||||
return this.args?.overMembersLimitGroupMentions.length;
|
||||
}
|
||||
|
||||
get hasTooManyMentions() {
|
||||
return this.args?.tooManyMentions;
|
||||
}
|
||||
|
||||
get hasUnreachableGroupMentions() {
|
||||
return this.unreachableGroupMentionsCount > 0;
|
||||
}
|
||||
|
||||
get hasOverMembersLimitGroupMentions() {
|
||||
return this.overMembersLimitMentionsCount > 0;
|
||||
}
|
||||
|
||||
get warningsCount() {
|
||||
return (
|
||||
this.unreachableGroupMentionsCount + this.overMembersLimitMentionsCount
|
||||
);
|
||||
}
|
||||
|
||||
get show() {
|
||||
return (
|
||||
this.hasTooManyMentions ||
|
||||
this.hasUnreachableGroupMentions ||
|
||||
this.hasOverMembersLimitGroupMentions
|
||||
);
|
||||
}
|
||||
|
||||
get listStyleClass() {
|
||||
if (this.hasTooManyMentions) {
|
||||
return "chat-mention-warnings-list__simple";
|
||||
}
|
||||
|
||||
if (this.warningsCount > 1) {
|
||||
return "chat-mention-warnings-list__multiple";
|
||||
} else {
|
||||
return "chat-mention-warnings-list__simple";
|
||||
}
|
||||
}
|
||||
|
||||
get warningHeaderText() {
|
||||
if (
|
||||
this.args?.mentionsCount <= this.warningsCount ||
|
||||
this.hasTooManyMentions
|
||||
) {
|
||||
return I18n.t("chat.mention_warning.groups.header.all");
|
||||
} else {
|
||||
return I18n.t("chat.mention_warning.groups.header.some");
|
||||
}
|
||||
}
|
||||
|
||||
get tooManyMentionsBody() {
|
||||
if (!this.hasTooManyMentions) {
|
||||
return;
|
||||
}
|
||||
|
||||
let notificationLimit = I18n.t(
|
||||
"chat.mention_warning.groups.notification_limit"
|
||||
);
|
||||
|
||||
if (this.currentUser.staff) {
|
||||
notificationLimit = htmlSafe(
|
||||
`<a
|
||||
target="_blank"
|
||||
href="/admin/site_settings/category/plugins?filter=max_mentions_per_chat_message"
|
||||
>
|
||||
${notificationLimit}
|
||||
</a>`
|
||||
);
|
||||
}
|
||||
|
||||
const settingLimit = I18n.t("chat.mention_warning.mentions_limit", {
|
||||
count: this.siteSettings.max_mentions_per_chat_message,
|
||||
});
|
||||
|
||||
return htmlSafe(
|
||||
I18n.t("chat.mention_warning.too_many_mentions", {
|
||||
notification_limit: notificationLimit,
|
||||
limit: settingLimit,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
get unreachableBody() {
|
||||
if (!this.hasUnreachableGroupMentions) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.unreachableGroupMentionsCount <= 2) {
|
||||
return I18n.t("chat.mention_warning.groups.unreachable", {
|
||||
group: this.args.unreachableGroupMentions[0],
|
||||
group_2: this.args.unreachableGroupMentions[1],
|
||||
count: this.unreachableGroupMentionsCount,
|
||||
});
|
||||
} else {
|
||||
return I18n.t("chat.mention_warning.groups.unreachable_multiple", {
|
||||
group: this.args.unreachableGroupMentions[0],
|
||||
count: this.unreachableGroupMentionsCount - 1, //N others
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get overMembersLimitBody() {
|
||||
if (!this.hasOverMembersLimitGroupMentions) {
|
||||
return;
|
||||
}
|
||||
|
||||
let notificationLimit = I18n.t(
|
||||
"chat.mention_warning.groups.notification_limit"
|
||||
);
|
||||
|
||||
if (this.currentUser.staff) {
|
||||
notificationLimit = htmlSafe(
|
||||
`<a
|
||||
target="_blank"
|
||||
href="/admin/site_settings/category/plugins?filter=max_users_notified_per_group_mention"
|
||||
>
|
||||
${notificationLimit}
|
||||
</a>`
|
||||
);
|
||||
}
|
||||
|
||||
const settingLimit = I18n.t("chat.mention_warning.groups.users_limit", {
|
||||
count: this.siteSettings.max_users_notified_per_group_mention,
|
||||
});
|
||||
|
||||
if (this.hasOverMembersLimitGroupMentions <= 2) {
|
||||
return htmlSafe(
|
||||
I18n.t("chat.mention_warning.groups.too_many_members", {
|
||||
group: this.args.overMembersLimitGroupMentions[0],
|
||||
group_2: this.args.overMembersLimitGroupMentions[1],
|
||||
count: this.overMembersLimitMentionsCount,
|
||||
notification_limit: notificationLimit,
|
||||
limit: settingLimit,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return htmlSafe(
|
||||
I18n.t("chat.mention_warning.groups.too_many_members_multiple", {
|
||||
group: this.args.overMembersLimitGroupMentions[0],
|
||||
count: this.overMembersLimitMentionsCount - 1, //N others
|
||||
notification_limit: notificationLimit,
|
||||
limit: settingLimit,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,25 +122,25 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.message.mentionWarning}}
|
||||
{{#if this.mentionWarning}}
|
||||
<div class="alert alert-info chat-message-mention-warning">
|
||||
{{#if this.message.mentionWarning.invitationSent}}
|
||||
{{#if this.mentionWarning.invitationSent}}
|
||||
{{d-icon "check"}}
|
||||
<span>
|
||||
{{i18n
|
||||
"chat.mention_warning.invitations_sent"
|
||||
count=this.message.mentionWarning.without_membership.length
|
||||
count=this.mentionWarning.without_membership.length
|
||||
}}
|
||||
</span>
|
||||
{{else}}
|
||||
<FlatButton @class="dismiss-mention-warning" @title="chat.mention_warning.dismiss" @action={{action "dismissMentionWarning"}} @icon="times" />
|
||||
|
||||
{{#if this.message.mentionWarning.cannot_see}}
|
||||
<p class="cannot-see">{{this.mentionedCannotSeeText}}</p>
|
||||
{{#if this.mentionWarning.cannot_see}}
|
||||
<p class="warning-item cannot-see">{{this.mentionedCannotSeeText}}</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.message.mentionWarning.without_membership}}
|
||||
<p class="without-membership">
|
||||
{{#if this.mentionWarning.without_membership}}
|
||||
<p class="warning-item without-membership">
|
||||
<span>{{this.mentionedWithoutMembershipText}}</span>
|
||||
<a
|
||||
class="invite-link"
|
||||
@@ -151,6 +151,13 @@
|
||||
</a>
|
||||
</p>
|
||||
{{/if}}
|
||||
{{#if this.mentionWarning.group_mentions_disabled}}
|
||||
<p class="warning-item">{{this.groupsWithDisabledMentions}}</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.mentionWarning.groups_with_too_many_members}}
|
||||
<p class="warning-item">{{this.groupsWithTooManyMembers}}</p>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
@@ -41,7 +41,6 @@ export default Component.extend({
|
||||
canInteractWithChat: false,
|
||||
isHovered: false,
|
||||
onHoverMessage: null,
|
||||
mentionWarning: null,
|
||||
chatEmojiReactionStore: service("chat-emoji-reaction-store"),
|
||||
chatEmojiPickerManager: service("chat-emoji-picker-manager"),
|
||||
adminTools: optionalService(),
|
||||
@@ -445,25 +444,56 @@ export default Component.extend({
|
||||
return Object.values(reactions).some((r) => r.count > 0);
|
||||
},
|
||||
|
||||
@discourseComputed("message.mentionWarning.cannot_see")
|
||||
@discourseComputed("message.mentionWarning")
|
||||
mentionWarning() {
|
||||
return this.message.mentionWarning;
|
||||
},
|
||||
|
||||
@discourseComputed("mentionWarning.cannot_see")
|
||||
mentionedCannotSeeText(users) {
|
||||
return I18n.t("chat.mention_warning.cannot_see", {
|
||||
usernames: users.mapBy("username").join(", "),
|
||||
username: users[0].username,
|
||||
count: users.length,
|
||||
others: this._othersTranslation(users.length - 1),
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed("message.mentionWarning.without_membership")
|
||||
@discourseComputed("mentionWarning.without_membership")
|
||||
mentionedWithoutMembershipText(users) {
|
||||
return I18n.t("chat.mention_warning.without_membership", {
|
||||
usernames: users.mapBy("username").join(", "),
|
||||
username: users[0].username,
|
||||
count: users.length,
|
||||
others: this._othersTranslation(users.length - 1),
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed("mentionWarning.group_mentions_disabled")
|
||||
groupsWithDisabledMentions(groups) {
|
||||
return I18n.t("chat.mention_warning.group_mentions_disabled", {
|
||||
group_name: groups[0],
|
||||
count: groups.length,
|
||||
others: this._othersTranslation(groups.length - 1),
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed("mentionWarning.groups_with_too_many_members")
|
||||
groupsWithTooManyMembers(groups) {
|
||||
return I18n.t("chat.mention_warning.too_many_members", {
|
||||
group_name: groups[0],
|
||||
count: groups.length,
|
||||
others: this._othersTranslation(groups.length - 1),
|
||||
});
|
||||
},
|
||||
|
||||
_othersTranslation(othersCount) {
|
||||
return I18n.t("chat.mention_warning.warning_multiple", {
|
||||
count: othersCount,
|
||||
});
|
||||
},
|
||||
|
||||
@action
|
||||
inviteMentioned() {
|
||||
const user_ids = this.message.mentionWarning.without_membership.mapBy("id");
|
||||
const user_ids = this.mentionWarning.without_membership.mapBy("id");
|
||||
|
||||
ajax(`/chat/${this.details.chat_channel_id}/invite`, {
|
||||
method: "PUT",
|
||||
|
||||
Reference in New Issue
Block a user