closes odoo/odoo#105677 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
179 lines
6.8 KiB
JavaScript
179 lines
6.8 KiB
JavaScript
/** @odoo-module **/
|
|
|
|
import { attr, clear, many, one, Model } from "@mail/model";
|
|
import { sprintf } from "@web/core/utils/strings";
|
|
|
|
Model({
|
|
name: "Composer",
|
|
identifyingMode: "xor",
|
|
recordMethods: {
|
|
/**
|
|
* @private
|
|
*/
|
|
_reset() {
|
|
this.update({
|
|
attachments: clear(),
|
|
rawMentionedChannels: clear(),
|
|
rawMentionedPartners: clear(),
|
|
textInputContent: clear(),
|
|
textInputCursorEnd: clear(),
|
|
textInputCursorStart: clear(),
|
|
textInputSelectionDirection: clear(),
|
|
});
|
|
for (const composerView of this.composerViews) {
|
|
composerView.update({ hasToRestoreContent: true });
|
|
}
|
|
},
|
|
},
|
|
fields: {
|
|
activeThread: one("Thread", {
|
|
required: true,
|
|
compute() {
|
|
if (
|
|
this.messageViewInEditing &&
|
|
this.messageViewInEditing.message &&
|
|
this.messageViewInEditing.message.originThread
|
|
) {
|
|
return this.messageViewInEditing.message.originThread;
|
|
}
|
|
if (this.thread) {
|
|
return this.thread;
|
|
}
|
|
return clear();
|
|
},
|
|
}),
|
|
/**
|
|
* States which attachments are currently being created in this composer.
|
|
*/
|
|
attachments: many("Attachment", { inverse: "composer" }),
|
|
canPostMessage: attr({
|
|
default: false,
|
|
compute() {
|
|
if (this.thread && !this.textInputContent && this.attachments.length === 0) {
|
|
return false;
|
|
}
|
|
return !this.hasUploadingAttachment && !this.isPostingMessage;
|
|
},
|
|
}),
|
|
composerViews: many("ComposerView", { inverse: "composer", isCausal: true }),
|
|
/**
|
|
* This field determines whether some attachments linked to this
|
|
* composer are being uploaded.
|
|
*/
|
|
hasUploadingAttachment: attr({
|
|
compute() {
|
|
return this.attachments.some((attachment) => attachment.isUploading);
|
|
},
|
|
}),
|
|
/**
|
|
* If true composer will log a note, else a comment will be posted.
|
|
*/
|
|
isLog: attr({ default: true }),
|
|
/**
|
|
* Determines whether a post_message request is currently pending.
|
|
*/
|
|
isPostingMessage: attr(),
|
|
mentionedChannels: many("Thread", {
|
|
/**
|
|
* Detects if mentioned channels are still in the composer text input content
|
|
* and removes them if not.
|
|
*/
|
|
compute() {
|
|
const mentionedChannels = [];
|
|
// ensure the same mention is not used multiple times if multiple
|
|
// channels have the same name
|
|
const namesIndex = {};
|
|
for (const channel of this.rawMentionedChannels) {
|
|
const fromIndex =
|
|
namesIndex[channel.name] !== undefined ? namesIndex[channel.name] + 1 : 0;
|
|
const index = this.textInputContent.indexOf(`#${channel.name}`, fromIndex);
|
|
if (index === -1) {
|
|
continue;
|
|
}
|
|
namesIndex[channel.name] = index;
|
|
mentionedChannels.push(channel);
|
|
}
|
|
return mentionedChannels;
|
|
},
|
|
}),
|
|
mentionedPartners: many("Partner", {
|
|
/**
|
|
* Detects if mentioned partners are still in the composer text input content
|
|
* and removes them if not.
|
|
*/
|
|
compute() {
|
|
const mentionedPartners = [];
|
|
// ensure the same mention is not used multiple times if multiple
|
|
// partners have the same name
|
|
const namesIndex = {};
|
|
for (const partner of this.rawMentionedPartners) {
|
|
const fromIndex =
|
|
namesIndex[partner.name] !== undefined ? namesIndex[partner.name] + 1 : 0;
|
|
const index = this.textInputContent.indexOf(`@${partner.name}`, fromIndex);
|
|
if (index === -1) {
|
|
continue;
|
|
}
|
|
namesIndex[partner.name] = index;
|
|
mentionedPartners.push(partner);
|
|
}
|
|
return mentionedPartners;
|
|
},
|
|
}),
|
|
messageViewInEditing: one("MessageView", {
|
|
identifying: true,
|
|
inverse: "composerForEditing",
|
|
}),
|
|
/**
|
|
* Placeholder displayed in the composer textarea when it's empty
|
|
*/
|
|
placeholder: attr({
|
|
compute() {
|
|
if (!this.thread) {
|
|
return "";
|
|
}
|
|
if (this.thread.channel) {
|
|
if (this.thread.channel.correspondent) {
|
|
return sprintf(
|
|
this.env._t("Message %s..."),
|
|
this.thread.channel.correspondent.nameOrDisplayName
|
|
);
|
|
}
|
|
return sprintf(this.env._t("Message #%s..."), this.thread.displayName);
|
|
}
|
|
if (this.isLog) {
|
|
return this.env._t("Log an internal note...");
|
|
}
|
|
return this.env._t("Send a message to followers...");
|
|
},
|
|
}),
|
|
rawMentionedChannels: many("Thread"),
|
|
rawMentionedPartners: many("Partner"),
|
|
/**
|
|
* Determines the extra `Partner` (on top of existing followers)
|
|
* that will receive the message being composed by `this`, and that will
|
|
* also be added as follower of `this.activeThread`.
|
|
*/
|
|
recipients: many("Partner", {
|
|
compute() {
|
|
const recipients = [...this.mentionedPartners];
|
|
if (this.activeThread && !this.isLog) {
|
|
for (const recipient of this.activeThread.suggestedRecipientInfoList) {
|
|
if (recipient.partner && recipient.isSelected) {
|
|
recipients.push(recipient.partner);
|
|
}
|
|
}
|
|
}
|
|
return recipients;
|
|
},
|
|
}),
|
|
textInputContent: attr({ default: "" }),
|
|
textInputCursorEnd: attr({ default: 0 }),
|
|
textInputCursorStart: attr({ default: 0 }),
|
|
textInputSelectionDirection: attr({ default: "none" }),
|
|
/**
|
|
* States the thread which this composer represents the state (if any).
|
|
*/
|
|
thread: one("Thread", { identifying: true, inverse: "composer" }),
|
|
},
|
|
});
|