This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.
osr-discourse-src/plugins/chat/assets/javascripts/discourse/services/chat-composer-warnings-tracker.js
Roman Rizzi 85e1a4934b
REFACTOR: Move mention warnings logic into a separate service. (#19465)
First follow-up to the feature introduced in #19034. We don't want to pollute main components like `chat-live-pane`, so we'll use a service to track and manage the state needed to display before-send warnings while composing a chat message.

We also move from acceptance tests to system specs.
2023-02-03 15:38:30 -03:00

147 lines
3.8 KiB
JavaScript

import Service, { inject as service } from "@ember/service";
import { ajax } from "discourse/lib/ajax";
import discourseDebounce from "discourse-common/lib/debounce";
import { bind } from "discourse-common/utils/decorators";
import { mentionRegex } from "pretty-text/mentions";
import { cancel } from "@ember/runloop";
import { tracked } from "@glimmer/tracking";
const MENTION_RESULT = {
invalid: -1,
unreachable: 0,
over_members_limit: 1,
};
const MENTION_DEBOUNCE_MS = 1000;
export default class ChatComposerWarningsTracker extends Service {
@service siteSettings;
// Track mention hints to display warnings
@tracked unreachableGroupMentions = [];
@tracked overMembersLimitGroupMentions = [];
@tracked tooManyMentions = false;
@tracked mentionsCount = 0;
@tracked mentionsTimer = null;
// Complimentary structure to avoid repeating mention checks.
_mentionWarningsSeen = {};
willDestroy() {
cancel(this.mentionsTimer);
}
@bind
trackMentions(message) {
this.mentionsTimer = discourseDebounce(
this,
this._trackMentions,
message,
MENTION_DEBOUNCE_MS
);
}
@bind
_trackMentions(message) {
if (!this.siteSettings.enable_mentions) {
return;
}
const mentions = this._extractMentions(message);
this.mentionsCount = mentions?.length;
if (this.mentionsCount > 0) {
this.tooManyMentions =
this.mentionsCount > this.siteSettings.max_mentions_per_chat_message;
if (!this.tooManyMentions) {
const newMentions = mentions.filter(
(mention) => !(mention in this._mentionWarningsSeen)
);
if (newMentions?.length > 0) {
this._recordNewWarnings(newMentions, mentions);
} else {
this._rebuildWarnings(mentions);
}
}
} else {
this.tooManyMentions = false;
this.unreachableGroupMentions = [];
this.overMembersLimitGroupMentions = [];
}
}
_extractMentions(message) {
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}`, "");
if (mentions.length > this.siteSettings.max_mentions_per_chat_message) {
mentionsLeft = false;
}
} else {
mentionsLeft = false;
}
}
return mentions;
}
_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.unreachableGroupMentions = newWarnings[0];
this.overMembersLimitGroupMentions = newWarnings[1];
}
}