DEV: Refactor ComposerMessages (#21077)
Co-authored-by: David Taylor <david@taylorhq.com>
This commit is contained in:
parent
db16700355
commit
788dc0a096
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue