DEV: Refactor ComposerMessages (#21077)

Co-authored-by: David Taylor <david@taylorhq.com>
This commit is contained in:
Jarek Radosz 2023-04-12 17:02:37 +02:00 committed by GitHub
parent db16700355
commit 788dc0a096
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 220 additions and 197 deletions

View File

@ -1,110 +1,76 @@
import Component from "@ember/component"; import Component from "@ember/component";
import { classNameBindings } from "@ember-decorators/component";
import EmberObject, { action } from "@ember/object"; import EmberObject, { action } from "@ember/object";
import I18n from "I18n"; import I18n from "I18n";
import LinkLookup from "discourse/lib/link-lookup"; import LinkLookup from "discourse/lib/link-lookup";
import { not } from "@ember/object/computed"; import { not } from "@ember/object/computed";
import { scheduleOnce } from "@ember/runloop";
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
let _messagesCache = {}; let _messagesCache = {};
let _recipient_names = []; let _recipient_names = [];
export default Component.extend({ @classNameBindings(":composer-popup-container", "hidden")
classNameBindings: [":composer-popup-container", "hidden"], export default class ComposerMessages extends Component {
checkedMessages: false, checkedMessages = false;
messages: null, messages = null;
messagesByTemplate: null, messagesByTemplate = null;
queuedForTyping: null, queuedForTyping = null;
_lastSimilaritySearch: null, similarTopics = null;
_similarTopicsMessage: null, usersNotSeen = null;
_yourselfConfirm: null,
similarTopics: null,
usersNotSeen: null,
hidden: not("composer.viewOpenOrFullscreen"), @not("composer.viewOpenOrFullscreen") hidden;
_lastSimilaritySearch = null;
_similarTopicsMessage = null;
didInsertElement() { didInsertElement() {
this._super(...arguments); super.didInsertElement(...arguments);
this.appEvents.on("composer:typed-reply", this, this._typedReply); this.appEvents.on("composer:typed-reply", this, this._typedReply);
this.appEvents.on("composer:opened", this, this._findMessages); this.appEvents.on("composer:opened", this, this._findMessages);
this.appEvents.on("composer:find-similar", this, this._findSimilar); this.appEvents.on("composer:find-similar", this, this._findSimilar);
this.appEvents.on("composer-messages:close", this, this._closeTop); this.appEvents.on("composer-messages:close", this, this._closeTop);
this.appEvents.on("composer-messages:create", this, this._create); this.appEvents.on("composer-messages:create", this, this._create);
scheduleOnce("afterRender", this, this.reset); this.reset();
}, }
willDestroyElement() { willDestroyElement() {
super.willDestroyElement(...arguments);
this.appEvents.off("composer:typed-reply", this, this._typedReply); this.appEvents.off("composer:typed-reply", this, this._typedReply);
this.appEvents.off("composer:opened", this, this._findMessages); this.appEvents.off("composer:opened", this, this._findMessages);
this.appEvents.off("composer:find-similar", this, this._findSimilar); this.appEvents.off("composer:find-similar", this, this._findSimilar);
this.appEvents.off("composer-messages:close", this, this._closeTop); this.appEvents.off("composer-messages:close", this, this._closeTop);
this.appEvents.off("composer-messages:create", this, this._create); this.appEvents.off("composer-messages:create", this, this._create);
}, }
_closeTop() { _closeTop() {
const messages = this.messages; if (this.isDestroying || this.isDestroyed) {
messages.popObject(); return;
this.set("messageCount", messages.get("length")); }
},
this.messages.popObject();
this.set("messageCount", this.messages.length);
}
_removeMessage(message) { _removeMessage(message) {
const messages = this.messages; this.messages.removeObject(message);
messages.removeObject(message); this.set("messageCount", this.messages.length);
this.set("messageCount", messages.get("length")); }
},
@action _create(info) {
closeMessage(message, event) { if (this.isDestroying || this.isDestroyed) {
event?.preventDefault(); return;
this._removeMessage(message); }
},
actions: { this.reset();
hideMessage(message) { this.popup(EmberObject.create(info));
this._removeMessage(message); }
// kind of hacky but the visibility depends on this
this.messagesByTemplate[message.get("templateName")] = undefined;
},
popup(message) {
const messagesByTemplate = this.messagesByTemplate;
const templateName = message.get("templateName");
if (!messagesByTemplate[templateName]) {
const messages = this.messages;
messages.pushObject(message);
this.set("messageCount", messages.get("length"));
messagesByTemplate[templateName] = message;
}
},
shareModal() {
const { topic } = this.composer;
const controller = showModal("share-topic", { model: topic.category });
controller.setProperties({
allowInvites:
topic.details.can_invite_to &&
!topic.archived &&
!topic.closed &&
!topic.deleted,
topic,
});
},
switchPM(message) {
this.composer.set("action", "privateMessage");
this.composer.set("targetRecipients", message.reply_username);
this._removeMessage(message);
},
},
// Resets all active messages. // Resets all active messages.
// For example if composing a new post. // For example if composing a new post.
reset() { reset() {
if (this.isDestroying || this.isDestroyed) {
return;
}
this.setProperties({ this.setProperties({
messages: [], messages: [],
messagesByTemplate: {}, messagesByTemplate: {},
@ -112,19 +78,41 @@ export default Component.extend({
checkedMessages: false, checkedMessages: false,
similarTopics: [], similarTopics: [],
}); });
}, }
// Called after the user has typed a reply. // Called after the user has typed a reply.
// Some messages only get shown after being typed. // Some messages only get shown after being typed.
_typedReply() { async _typedReply() {
if (this.isDestroying || this.isDestroyed) { if (this.isDestroying || this.isDestroyed) {
return; return;
} }
const composer = this.composer; for (const msg of this.queuedForTyping) {
if (composer.get("privateMessage")) { if (this.composer.whisper && msg.hide_if_whisper) {
const recipients = composer.targetRecipientsArray; return;
const recipient_names = recipients }
this.popup(msg);
}
if (this.composer.privateMessage) {
if (
this.composer.targetRecipientsArray.length > 0 &&
this.composer.targetRecipientsArray.every(
(r) => r.name === this.currentUser.username
)
) {
const message = this.composer.store.createRecord("composer-message", {
id: "yourself_confirm",
templateName: "education",
title: I18n.t("composer.yourself_confirm.title"),
body: I18n.t("composer.yourself_confirm.body"),
});
this.popup(message);
}
const recipient_names = this.composer.targetRecipientsArray
.filter((r) => r.type === "user") .filter((r) => r.type === "user")
.map(({ name }) => name); .map(({ name }) => name);
@ -135,83 +123,69 @@ export default Component.extend({
) { ) {
_recipient_names = recipient_names; _recipient_names = recipient_names;
ajax(`/composer_messages/user_not_seen_in_a_while`, { const response = await ajax(
type: "GET", `/composer_messages/user_not_seen_in_a_while`,
data: { {
usernames: recipient_names, type: "GET",
}, data: {
}).then((response) => { usernames: recipient_names,
if ( },
response.user_count > 0 &&
this.get("usersNotSeen") !== response.usernames.join("-")
) {
this.set("usersNotSeen", response.usernames.join("-"));
this.messagesByTemplate["education"] = undefined;
let usernames = [];
response.usernames.forEach((username, index) => {
usernames[
index
] = `<a class='mention' href='/u/${username}'>@${username}</a>`;
});
let body_key = "composer.user_not_seen_in_a_while.single";
if (response.user_count > 1) {
body_key = "composer.user_not_seen_in_a_while.multiple";
}
const message = composer.store.createRecord("composer-message", {
id: "user-not-seen",
templateName: "education",
body: I18n.t(body_key, {
usernames: usernames.join(", "),
time_ago: response.time_ago,
}),
});
this.send("popup", message);
} }
}); );
}
if ( if (this.isDestroying || this.isDestroyed) {
recipients.length > 0 && return;
recipients.every((r) => r.name === this.currentUser.get("username")) }
) {
const message = if (
this._yourselfConfirm || response.user_count > 0 &&
composer.store.createRecord("composer-message", { this.usersNotSeen !== response.usernames.join("-")
id: "yourself_confirm", ) {
templateName: "education", this.set("usersNotSeen", response.usernames.join("-"));
title: I18n.t("composer.yourself_confirm.title"), this.messagesByTemplate["education"] = undefined;
body: I18n.t("composer.yourself_confirm.body"),
let usernames = [];
response.usernames.forEach((username, index) => {
usernames[
index
] = `<a class='mention' href='/u/${username}'>@${username}</a>`;
}); });
this.send("popup", message);
let body_key;
if (response.user_count === 1) {
body_key = "composer.user_not_seen_in_a_while.single";
} else {
body_key = "composer.user_not_seen_in_a_while.multiple";
}
const message = this.composer.store.createRecord("composer-message", {
id: "user-not-seen",
templateName: "education",
body: I18n.t(body_key, {
usernames: usernames.join(", "),
time_ago: response.time_ago,
}),
});
this.popup(message);
}
} }
} }
}
this.queuedForTyping.forEach((msg) => { async _findSimilar() {
if (composer.whisper && msg.hide_if_whisper) { if (this.isDestroying || this.isDestroyed) {
return;
}
this.send("popup", msg);
});
},
_create(info) {
this.reset();
this.send("popup", EmberObject.create(info));
},
_findSimilar() {
const composer = this.composer;
// We don't care about similar topics unless creating a topic
if (!composer.get("creatingTopic")) {
return; return;
} }
// TODO pass the 200 in from somewhere // We don't care about similar topics unless creating a topic
const raw = (composer.get("reply") || "").slice(0, 200); if (!this.composer.creatingTopic) {
const title = composer.get("title") || ""; return;
}
// TODO: pass the 200 in from somewhere
const raw = (this.composer.reply || "").slice(0, 200);
const title = this.composer.title || "";
// Ensure we have at least a title // Ensure we have at least a title
if (title.length < this.siteSettings.min_title_similar_length) { if (title.length < this.siteSettings.min_title_similar_length) {
@ -223,83 +197,132 @@ export default Component.extend({
if (concat === this._lastSimilaritySearch) { if (concat === this._lastSimilaritySearch) {
return; return;
} }
this._lastSimilaritySearch = concat;
const similarTopics = this.similarTopics; this._lastSimilaritySearch = concat;
const message = this._similarTopicsMessage ||= this.composer.store.createRecord(
this._similarTopicsMessage || "composer-message",
composer.store.createRecord("composer-message", { {
id: "similar_topics", id: "similar_topics",
templateName: "similar-topics", templateName: "similar-topics",
extraClass: "similar-topics", extraClass: "similar-topics",
});
this._similarTopicsMessage = message;
composer.store.find("similar-topic", { title, raw }).then((topics) => {
if (this.isDestroying || this.isDestroyed) {
return;
} }
);
similarTopics.clear(); const topics = await this.composer.store.find("similar-topic", {
similarTopics.pushObjects(topics.get("content")); title,
raw,
if (similarTopics.get("length") > 0) {
message.set("similarTopics", similarTopics);
this.send("popup", message);
} else if (message) {
this.send("hideMessage", message);
}
}); });
},
if (this.isDestroying || this.isDestroyed) {
return;
}
this.similarTopics.clear();
this.similarTopics.pushObjects(topics.content);
if (this.similarTopics.length > 0) {
this._similarTopicsMessage.set("similarTopics", this.similarTopics);
this.popup(this._similarTopicsMessage);
} else if (this._similarTopicsMessage) {
this.hideMessage(this._similarTopicsMessage);
}
}
// Figure out if there are any messages that should be displayed above the composer. // Figure out if there are any messages that should be displayed above the composer.
_findMessages() { async _findMessages() {
if (this.isDestroying || this.isDestroyed) {
return;
}
if (this.checkedMessages) { if (this.checkedMessages) {
return; return;
} }
const composer = this.composer; const args = { composer_action: this.composer.action };
const args = { composer_action: composer.get("action") }; const topicId = this.composer.topic?.id;
const topicId = composer.get("topic.id"); const postId = this.composer.post?.id;
const postId = composer.get("post.id");
if (topicId) { if (topicId) {
args.topic_id = topicId; args.topic_id = topicId;
} }
if (postId) { if (postId) {
args.post_id = postId; args.post_id = postId;
} }
const cacheKey = `${args.composer_action}${args.topic_id}${args.post_id}`; const cacheKey = `${args.composer_action}${args.topic_id}${args.post_id}`;
const processMessages = (messages) => { let messages;
if (_messagesCache.cacheKey === cacheKey) {
messages = _messagesCache.messages;
} else {
messages = await this.composer.store.find("composer-message", args);
if (this.isDestroying || this.isDestroyed) { if (this.isDestroying || this.isDestroyed) {
return; return;
} }
// Checking composer messages on replies can give us a list of links to check for _messagesCache = { messages, cacheKey };
// duplicates
if (messages.extras && messages.extras.duplicate_lookup) {
this.addLinkLookup(new LinkLookup(messages.extras.duplicate_lookup));
}
this.set("checkedMessages", true);
const queuedForTyping = this.queuedForTyping;
messages.forEach((msg) =>
msg.wait_for_typing
? queuedForTyping.addObject(msg)
: this.send("popup", msg)
);
};
if (_messagesCache.cacheKey === cacheKey) {
processMessages(_messagesCache.messages);
} else {
composer.store.find("composer-message", args).then((messages) => {
_messagesCache = { messages, cacheKey };
processMessages(messages);
});
} }
},
}); // Checking composer messages on replies can give us a list of links to check for
// duplicates
if (messages.extras?.duplicate_lookup) {
this.addLinkLookup(new LinkLookup(messages.extras.duplicate_lookup));
}
this.set("checkedMessages", true);
for (const msg of messages) {
if (msg.wait_for_typing) {
this.queuedForTyping.addObject(msg);
} else {
this.popup(msg);
}
}
}
@action
closeMessage(message, event) {
event?.preventDefault();
this._removeMessage(message);
}
@action
hideMessage(message) {
this._removeMessage(message);
// kind of hacky but the visibility depends on this
this.messagesByTemplate[message.templateName] = undefined;
}
@action
popup(message) {
if (!this.messagesByTemplate[message.templateName]) {
this.messages.pushObject(message);
this.set("messageCount", this.messages.length);
this.messagesByTemplate[message.templateName] = message;
}
}
@action
shareModal() {
const { topic } = this.composer;
const controller = showModal("share-topic", { model: topic.category });
controller.setProperties({
allowInvites:
topic.details.can_invite_to &&
!topic.archived &&
!topic.closed &&
!topic.deleted,
topic,
});
}
@action
switchPM(message) {
this.composer.set("action", "privateMessage");
this.composer.set("targetRecipients", message.reply_username);
this._removeMessage(message);
}
}