mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-20 23:52:09 +02:00
Initial commit: Core packages
This commit is contained in:
commit
12c29a983b
9512 changed files with 8379910 additions and 0 deletions
751
odoo-bringout-oca-ocb-mail/mail/static/src/models/message.js
Normal file
751
odoo-bringout-oca-ocb-mail/mail/static/src/models/message.js
Normal file
|
|
@ -0,0 +1,751 @@
|
|||
/** @odoo-module **/
|
||||
|
||||
import { registerModel } from '@mail/model/model_core';
|
||||
import { attr, many, one } from '@mail/model/model_field';
|
||||
import { clear, insert } from '@mail/model/model_field_command';
|
||||
import { addLink, htmlToTextContentInline, parseAndTransform } from '@mail/js/utils';
|
||||
|
||||
import { session } from '@web/session';
|
||||
|
||||
import { getLangDatetimeFormat, str_to_datetime } from 'web.time';
|
||||
|
||||
const { markup } = owl;
|
||||
|
||||
registerModel({
|
||||
name: 'Message',
|
||||
modelMethods: {
|
||||
/**
|
||||
* @param {Object} data
|
||||
* @return {Object}
|
||||
*/
|
||||
convertData(data) {
|
||||
const data2 = {};
|
||||
data2.attachments = data.attachment_ids;
|
||||
if ('author' in data) {
|
||||
data2.author = data.author;
|
||||
}
|
||||
if ('body' in data) {
|
||||
data2.body = data.body;
|
||||
}
|
||||
if ('date' in data && data.date) {
|
||||
data2.date = moment(str_to_datetime(data.date));
|
||||
}
|
||||
if ('email_from' in data) {
|
||||
data2.email_from = data.email_from;
|
||||
}
|
||||
if ('guestAuthor' in data) {
|
||||
data2.guestAuthor = data.guestAuthor;
|
||||
}
|
||||
if ('history_partner_ids' in data && this.messaging.currentPartner) {
|
||||
data2.isHistory = data.history_partner_ids.includes(this.messaging.currentPartner.id);
|
||||
}
|
||||
if ('id' in data) {
|
||||
data2.id = data.id;
|
||||
}
|
||||
if ('is_discussion' in data) {
|
||||
data2.is_discussion = data.is_discussion;
|
||||
}
|
||||
if ('is_note' in data) {
|
||||
data2.is_note = data.is_note;
|
||||
}
|
||||
if ('is_notification' in data) {
|
||||
data2.is_notification = data.is_notification;
|
||||
}
|
||||
data2.linkPreviews = data.linkPreviews;
|
||||
if ('messageReactionGroups' in data) {
|
||||
data2.messageReactionGroups = data.messageReactionGroups;
|
||||
}
|
||||
if ('message_type' in data) {
|
||||
data2.message_type = data.message_type;
|
||||
}
|
||||
if ('model' in data && 'res_id' in data && data.model && data.res_id) {
|
||||
const originThreadData = {
|
||||
id: data.res_id,
|
||||
model: data.model,
|
||||
};
|
||||
if ('record_name' in data && data.record_name) {
|
||||
originThreadData.name = data.record_name;
|
||||
}
|
||||
if ('res_model_name' in data && data.res_model_name) {
|
||||
originThreadData.model_name = data.res_model_name;
|
||||
}
|
||||
if ('module_icon' in data) {
|
||||
originThreadData.moduleIcon = data.module_icon;
|
||||
}
|
||||
data2.originThread = originThreadData;
|
||||
}
|
||||
if ('needaction_partner_ids' in data && this.messaging.currentPartner) {
|
||||
data2.isNeedaction = data.needaction_partner_ids.includes(this.messaging.currentPartner.id);
|
||||
}
|
||||
if ('notifications' in data) {
|
||||
data2.notifications = insert(data.notifications.map(notificationData =>
|
||||
this.messaging.models['Notification'].convertData(notificationData)
|
||||
));
|
||||
}
|
||||
if ('parentMessage' in data) {
|
||||
if (!data.parentMessage) {
|
||||
data2.parentMessage = clear();
|
||||
} else {
|
||||
data2.parentMessage = this.convertData(data.parentMessage);
|
||||
}
|
||||
}
|
||||
if ('recipients' in data) {
|
||||
data2.recipients = data.recipients;
|
||||
}
|
||||
if ('starred_partner_ids' in data && this.messaging.currentPartner) {
|
||||
data2.isStarred = data.starred_partner_ids.includes(this.messaging.currentPartner.id);
|
||||
}
|
||||
if ('subject' in data) {
|
||||
data2.subject = data.subject;
|
||||
}
|
||||
if ('subtype_description' in data) {
|
||||
data2.subtype_description = data.subtype_description;
|
||||
}
|
||||
if ('subtype_id' in data) {
|
||||
data2.subtype_id = data.subtype_id;
|
||||
}
|
||||
if ('trackingValues' in data) {
|
||||
data2.trackingValues = data.trackingValues;
|
||||
}
|
||||
|
||||
return data2;
|
||||
},
|
||||
/**
|
||||
* Mark all messages of current user with given domain as read.
|
||||
*
|
||||
* @param {Array[]} domain
|
||||
*/
|
||||
async markAllAsRead(domain) {
|
||||
await this.messaging.rpc({
|
||||
model: 'mail.message',
|
||||
method: 'mark_all_as_read',
|
||||
kwargs: { domain },
|
||||
}, { shadow: true });
|
||||
},
|
||||
/**
|
||||
* Mark provided messages as read. Messages that have been marked as
|
||||
* read are acknowledged by server with response as bus.
|
||||
* notification of following format:
|
||||
*
|
||||
* [[dbname, 'res.partner', partnerId], { type: 'mark_as_read' }]
|
||||
*
|
||||
* @see MessagingNotificationHandler:_handleNotificationPartnerMarkAsRead()
|
||||
*
|
||||
* @param {Message[]} messages
|
||||
*/
|
||||
async markAsRead(messages) {
|
||||
await this.messaging.rpc({
|
||||
model: 'mail.message',
|
||||
method: 'set_message_done',
|
||||
args: [messages.map(message => message.id)]
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Performs the given `route` RPC to fetch messages.
|
||||
*
|
||||
* @param {string} route
|
||||
* @param {Object} params
|
||||
* @returns {Message[]}
|
||||
*/
|
||||
async performRpcMessageFetch(route, params) {
|
||||
const messagesData = await this.messaging.rpc({ route, params }, { shadow: true });
|
||||
if (!this.messaging) {
|
||||
return;
|
||||
}
|
||||
const messages = this.messaging.models['Message'].insert(messagesData.map(
|
||||
messageData => this.messaging.models['Message'].convertData(messageData)
|
||||
));
|
||||
// compute seen indicators (if applicable)
|
||||
for (const message of messages) {
|
||||
for (const thread of message.threads) {
|
||||
if (!thread.channel || thread.channel.channel_type === 'channel') {
|
||||
// disabled on non-channel threads and
|
||||
// on `channel` channels for performance reasons
|
||||
continue;
|
||||
}
|
||||
this.messaging.models['MessageSeenIndicator'].insert({
|
||||
thread,
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
},
|
||||
/**
|
||||
* Unstar all starred messages of current user.
|
||||
*/
|
||||
async unstarAll() {
|
||||
await this.messaging.rpc({
|
||||
model: 'mail.message',
|
||||
method: 'unstar_all',
|
||||
});
|
||||
},
|
||||
},
|
||||
recordMethods: {
|
||||
/**
|
||||
* Adds the given reaction on this message.
|
||||
*
|
||||
* @param {string} content
|
||||
*/
|
||||
async addReaction(content) {
|
||||
const messageData = await this.messaging.rpc({
|
||||
route: '/mail/message/add_reaction',
|
||||
params: { content, message_id: this.id },
|
||||
});
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
}
|
||||
this.update(messageData);
|
||||
},
|
||||
/**
|
||||
* Mark this message as read, so that it no longer appears in current
|
||||
* partner Inbox.
|
||||
*/
|
||||
async markAsRead() {
|
||||
await this.messaging.rpc({
|
||||
model: 'mail.message',
|
||||
method: 'set_message_done',
|
||||
args: [[this.id]]
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Opens the view that allows to resend the message in case of failure.
|
||||
*/
|
||||
openResendAction() {
|
||||
this.env.services.action.doAction(
|
||||
'mail.mail_resend_message_action',
|
||||
{
|
||||
additionalContext: {
|
||||
mail_message_to_resend: this.id,
|
||||
},
|
||||
}
|
||||
);
|
||||
},
|
||||
/**
|
||||
* Removes the given reaction from this message.
|
||||
*
|
||||
* @param {string} content
|
||||
*/
|
||||
async removeReaction(content) {
|
||||
const messageData = await this.messaging.rpc({
|
||||
route: '/mail/message/remove_reaction',
|
||||
params: { content, message_id: this.id },
|
||||
});
|
||||
if (!this.exists()) {
|
||||
return;
|
||||
}
|
||||
this.update(messageData);
|
||||
},
|
||||
/**
|
||||
* Toggle the starred status of the provided message.
|
||||
*/
|
||||
async toggleStar() {
|
||||
await this.messaging.rpc({
|
||||
model: 'mail.message',
|
||||
method: 'toggle_message_starred',
|
||||
args: [[this.id]]
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Updates the message's content.
|
||||
*
|
||||
* @param {Object} param0
|
||||
* @param {string} param0.body the new body of the message
|
||||
* @param {number[]} param0.attachment_ids
|
||||
* @param {string[]} param0.attachment_tokens
|
||||
*/
|
||||
async updateContent({ body, attachment_ids, attachment_tokens }) {
|
||||
const messageData = await this.messaging.rpc({
|
||||
route: '/mail/message/update_content',
|
||||
params: {
|
||||
body,
|
||||
attachment_ids,
|
||||
attachment_tokens,
|
||||
message_id: this.id,
|
||||
},
|
||||
});
|
||||
if (!this.messaging) {
|
||||
return;
|
||||
}
|
||||
this.messaging.models['Message'].insert(messageData);
|
||||
},
|
||||
},
|
||||
fields: {
|
||||
authorName: attr({
|
||||
compute() {
|
||||
if (this.author) {
|
||||
return this.author.nameOrDisplayName;
|
||||
}
|
||||
if (this.guestAuthor) {
|
||||
return this.guestAuthor.name;
|
||||
}
|
||||
if (this.email_from) {
|
||||
return this.email_from;
|
||||
}
|
||||
return this.env._t("Anonymous");
|
||||
},
|
||||
}),
|
||||
attachments: many('Attachment', {
|
||||
inverse: 'messages',
|
||||
}),
|
||||
author: one('Partner'),
|
||||
avatarUrl: attr({
|
||||
compute() {
|
||||
if (this.author && (!this.originThread || this.originThread.model !== 'mail.channel')) {
|
||||
// TODO FIXME for public user this might not be accessible. task-2223236
|
||||
// we should probably use the correspondig attachment id + access token
|
||||
// or create a dedicated route to get message image, checking the access right of the message
|
||||
return this.author.avatarUrl;
|
||||
} else if (this.author && this.originThread && this.originThread.model === 'mail.channel') {
|
||||
return `/mail/channel/${this.originThread.id}/partner/${this.author.id}/avatar_128`;
|
||||
} else if (this.guestAuthor && (!this.originThread || this.originThread.model !== 'mail.channel')) {
|
||||
return this.guestAuthor.avatarUrl;
|
||||
} else if (this.guestAuthor && this.originThread && this.originThread.model === 'mail.channel') {
|
||||
return `/mail/channel/${this.originThread.id}/guest/${this.guestAuthor.id}/avatar_128?unique=${this.guestAuthor.name}`;
|
||||
} else if (this.message_type === 'email') {
|
||||
return '/mail/static/src/img/email_icon.png';
|
||||
}
|
||||
return '/mail/static/src/img/smiley/avatar.jpg';
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* This value is meant to be returned by the server
|
||||
* (and has been sanitized before stored into db).
|
||||
* Do not use this value in a 't-raw' if the message has been created
|
||||
* directly from user input and not from server data as it's not escaped.
|
||||
*/
|
||||
body: attr({
|
||||
default: "",
|
||||
}),
|
||||
/**
|
||||
* Whether this message can be deleted.
|
||||
*/
|
||||
canBeDeleted: attr({
|
||||
compute() {
|
||||
if (!session.is_admin && !this.isCurrentUserOrGuestAuthor) {
|
||||
return false;
|
||||
}
|
||||
if (!this.originThread) {
|
||||
return false;
|
||||
}
|
||||
if (this.trackingValues.length > 0) {
|
||||
return false;
|
||||
}
|
||||
if (this.message_type !== 'comment') {
|
||||
return false;
|
||||
}
|
||||
if (this.originThread.model === 'mail.channel') {
|
||||
return true;
|
||||
}
|
||||
return this.is_note;
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* Whether this message can be starred/unstarred.
|
||||
*/
|
||||
canStarBeToggled: attr({
|
||||
compute() {
|
||||
return !this.messaging.isCurrentUserGuest && !this.isTemporary && !this.isTransient;
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* Determines the date of the message as a moment object.
|
||||
*/
|
||||
date: attr(),
|
||||
/**
|
||||
* States the date of this message as a string (either a relative period
|
||||
* in the near past or an actual date for older dates).
|
||||
*/
|
||||
dateDay: attr({
|
||||
compute() {
|
||||
if (!this.date) {
|
||||
// Without a date, we assume that it's a today message. This is
|
||||
// mainly done to avoid flicker inside the UI.
|
||||
return this.env._t("Today");
|
||||
}
|
||||
const date = this.date.format('YYYY-MM-DD');
|
||||
if (date === moment().format('YYYY-MM-DD')) {
|
||||
return this.env._t("Today");
|
||||
} else if (
|
||||
date === moment()
|
||||
.subtract(1, 'days')
|
||||
.format('YYYY-MM-DD')
|
||||
) {
|
||||
return this.env._t("Yesterday");
|
||||
}
|
||||
return this.date.format('LL');
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* The date time of the message at current user locale time.
|
||||
*/
|
||||
datetime: attr({
|
||||
compute() {
|
||||
if (!this.date) {
|
||||
return clear();
|
||||
}
|
||||
return this.date.format(getLangDatetimeFormat());
|
||||
},
|
||||
}),
|
||||
email_from: attr(),
|
||||
failureNotifications: many('Notification', {
|
||||
compute() {
|
||||
return this.notifications.filter(notifications => notifications.isFailure);
|
||||
},
|
||||
}),
|
||||
guestAuthor: one('Guest', {
|
||||
inverse: 'authoredMessages',
|
||||
}),
|
||||
/**
|
||||
* States whether the message has some attachments.
|
||||
*/
|
||||
hasAttachments: attr({
|
||||
compute() {
|
||||
return this.attachments.length > 0;
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* Determines whether the message has a reaction icon.
|
||||
*/
|
||||
hasReactionIcon: attr({
|
||||
compute() {
|
||||
return !this.isTemporary && !this.isTransient;
|
||||
},
|
||||
}),
|
||||
id: attr({
|
||||
identifying: true,
|
||||
}),
|
||||
isCurrentUserOrGuestAuthor: attr({
|
||||
compute() {
|
||||
return !!(
|
||||
this.author &&
|
||||
this.messaging.currentPartner &&
|
||||
this.messaging.currentPartner === this.author
|
||||
) || !!(
|
||||
this.guestAuthor &&
|
||||
this.messaging.currentGuest &&
|
||||
this.messaging.currentGuest === this.guestAuthor
|
||||
);
|
||||
},
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* States if the body field is empty, regardless of editor default
|
||||
* html content. To determine if a message is fully empty, use
|
||||
* `isEmpty`.
|
||||
*/
|
||||
isBodyEmpty: attr({
|
||||
compute() {
|
||||
return (
|
||||
!this.body ||
|
||||
[
|
||||
'',
|
||||
'<p></p>',
|
||||
'<p><br></p>',
|
||||
'<p><br/></p>',
|
||||
].includes(this.body.replace(/\s/g, ''))
|
||||
);
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* States whether `body` and `subtype_description` contain similar
|
||||
* values.
|
||||
*
|
||||
* This is necessary to avoid displaying both of them together when they
|
||||
* contain duplicate information. This will especially happen with
|
||||
* messages that are posted automatically at the creation of a record
|
||||
* (messages that serve as tracking messages). They do have hard-coded
|
||||
* "record created" body while being assigned a subtype with a
|
||||
* description that states the same information.
|
||||
*
|
||||
* Fixing newer messages is possible by not assigning them a duplicate
|
||||
* body content, but the check here is still necessary to handle
|
||||
* existing messages.
|
||||
*
|
||||
* Limitations:
|
||||
* - A translated subtype description might not match a non-translatable
|
||||
* body created by a user with a different language.
|
||||
* - Their content might be mostly but not exactly the same.
|
||||
*/
|
||||
isBodyEqualSubtypeDescription: attr({
|
||||
compute() {
|
||||
if (!this.body || !this.subtype_description) {
|
||||
return false;
|
||||
}
|
||||
const inlineBody = htmlToTextContentInline(this.body);
|
||||
return inlineBody.toLowerCase() === this.subtype_description.toLowerCase();
|
||||
},
|
||||
default: false,
|
||||
}),
|
||||
isDiscussionOrNotification: attr({
|
||||
compute() {
|
||||
if (this.is_discussion || this.is_notification || this.message_type === "auto_comment") {
|
||||
return true;
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* Determine whether the message has to be considered empty or not.
|
||||
*
|
||||
* An empty message has no text, no attachment and no tracking value.
|
||||
*/
|
||||
isEmpty: attr({
|
||||
/**
|
||||
* The method does not attempt to cover all possible cases of empty
|
||||
* messages, but mostly those that happen with a standard flow. Indeed
|
||||
* it is preferable to be defensive and show an empty message sometimes
|
||||
* instead of hiding a non-empty message.
|
||||
*
|
||||
* The main use case for when a message should become empty is for a
|
||||
* message posted with only an attachment (no body) and then the
|
||||
* attachment is deleted.
|
||||
*
|
||||
* The main use case for being defensive with the check is when
|
||||
* receiving a message that has no textual content but has other
|
||||
* meaningful HTML tags (eg. just an <img/>).
|
||||
*/
|
||||
compute() {
|
||||
return (
|
||||
this.isBodyEmpty &&
|
||||
!this.hasAttachments &&
|
||||
this.trackingValues.length === 0 &&
|
||||
!this.subtype_description
|
||||
);
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* States whether `originThread.name` and `subject` contain similar
|
||||
* values except it contains the extra prefix at the start
|
||||
* of the subject.
|
||||
*
|
||||
* This is necessary to avoid displaying the subject, if
|
||||
* the subject is same as threadname.
|
||||
*/
|
||||
isSubjectSimilarToOriginThreadName: attr({
|
||||
compute() {
|
||||
if (
|
||||
!this.subject ||
|
||||
!this.originThread ||
|
||||
!this.originThread.name
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const threadName = this.originThread.name.toLowerCase().trim();
|
||||
const prefixList = ['re:', 'fw:', 'fwd:'];
|
||||
let cleanedSubject = this.subject.toLowerCase();
|
||||
let wasSubjectCleaned = true;
|
||||
while (wasSubjectCleaned) {
|
||||
wasSubjectCleaned = false;
|
||||
if (threadName === cleanedSubject) {
|
||||
return true;
|
||||
}
|
||||
for (const prefix of prefixList) {
|
||||
if (cleanedSubject.startsWith(prefix)) {
|
||||
cleanedSubject = cleanedSubject.replace(prefix, '').trim();
|
||||
wasSubjectCleaned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}),
|
||||
isTemporary: attr({
|
||||
default: false,
|
||||
}),
|
||||
isTransient: attr({
|
||||
default: false,
|
||||
}),
|
||||
is_discussion: attr({
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* Determine whether the message was a needaction. Useful to make it
|
||||
* present in history mailbox.
|
||||
*/
|
||||
isHistory: attr({
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* Determine whether the message is needaction. Useful to make it
|
||||
* present in inbox mailbox and messaging menu.
|
||||
*/
|
||||
isNeedaction: attr({
|
||||
default: false,
|
||||
}),
|
||||
is_note: attr({
|
||||
default: false,
|
||||
}),
|
||||
is_notification: attr({
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* Determine whether the current partner is mentioned.
|
||||
*/
|
||||
isCurrentPartnerMentioned: attr({
|
||||
compute() {
|
||||
return this.recipients.includes(this.messaging.currentPartner);
|
||||
},
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* Determine whether the message is highlighted.
|
||||
*/
|
||||
isHighlighted: attr({
|
||||
compute() {
|
||||
return (
|
||||
this.isCurrentPartnerMentioned &&
|
||||
this.originThread &&
|
||||
this.originThread.model === 'mail.channel'
|
||||
);
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* Determine whether the message is starred. Useful to make it present
|
||||
* in starred mailbox.
|
||||
*/
|
||||
isStarred: attr({
|
||||
default: false,
|
||||
}),
|
||||
/**
|
||||
* Last tracking value of the message.
|
||||
*/
|
||||
lastTrackingValue: one('TrackingValue', {
|
||||
compute() {
|
||||
const {
|
||||
length: l,
|
||||
[l - 1]: lastTrackingValue,
|
||||
} = this.trackingValues;
|
||||
if (lastTrackingValue) {
|
||||
return lastTrackingValue;
|
||||
}
|
||||
return clear();
|
||||
},
|
||||
}),
|
||||
linkPreviews: many('LinkPreview', {
|
||||
inverse: 'message',
|
||||
}),
|
||||
/**
|
||||
* Groups of reactions per content allowing to know the number of
|
||||
* reactions for each.
|
||||
*/
|
||||
messageReactionGroups: many('MessageReactionGroup', {
|
||||
inverse: 'message',
|
||||
}),
|
||||
messageTypeText: attr({
|
||||
compute() {
|
||||
if (this.message_type === 'notification') {
|
||||
return this.env._t("System notification");
|
||||
}
|
||||
if (this.message_type === "auto_comment") {
|
||||
return this.env._t("Automated message");
|
||||
}
|
||||
if (!this.is_discussion && !this.is_notification) {
|
||||
return this.env._t("Note");
|
||||
}
|
||||
return this.env._t("Message");
|
||||
},
|
||||
}),
|
||||
message_type: attr(),
|
||||
notificationMessageViews: many('NotificationMessageView', {
|
||||
inverse: 'message',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* States the views that are displaying this message.
|
||||
*/
|
||||
messageViews: many('MessageView', {
|
||||
inverse: 'message',
|
||||
isCausal: true,
|
||||
}),
|
||||
messageListViewItems: many('MessageListViewItem', {
|
||||
inverse: 'message',
|
||||
}),
|
||||
notifications: many('Notification', {
|
||||
inverse: 'message',
|
||||
isCausal: true,
|
||||
}),
|
||||
/**
|
||||
* Origin thread of this message (if any).
|
||||
*/
|
||||
originThread: one('Thread', {
|
||||
inverse: 'messagesAsOriginThread',
|
||||
}),
|
||||
/**
|
||||
* States the message that this message replies to (if any). Only makes
|
||||
* sense on channels. Other types of threads might have a parent message
|
||||
* (parent_id in python) that should be ignored for the purpose of this
|
||||
* feature.
|
||||
*/
|
||||
parentMessage: one('Message'),
|
||||
/**
|
||||
* This value is meant to be based on field body which is
|
||||
* returned by the server (and has been sanitized before stored into db).
|
||||
* Do not use this value in a 't-raw' if the message has been created
|
||||
* directly from user input and not from server data as it's not escaped.
|
||||
*/
|
||||
prettyBody: attr({
|
||||
/**
|
||||
* This value is meant to be based on field body which is
|
||||
* returned by the server (and has been sanitized before stored into db).
|
||||
* Do not use this value in a 't-raw' if the message has been created
|
||||
* directly from user input and not from server data as it's not escaped.
|
||||
*/
|
||||
compute() {
|
||||
if (!this.body) {
|
||||
// body null in db, body will be false instead of empty string
|
||||
return clear();
|
||||
}
|
||||
// add anchor tags to urls
|
||||
return parseAndTransform(this.body, addLink);
|
||||
},
|
||||
default: "",
|
||||
}),
|
||||
prettyBodyAsMarkup: attr({
|
||||
compute() {
|
||||
return markup(this.prettyBody);
|
||||
},
|
||||
}),
|
||||
recipients: many('Partner'),
|
||||
shortTime: attr({
|
||||
compute() {
|
||||
if (!this.date) {
|
||||
return clear();
|
||||
}
|
||||
return this.date.format('hh:mm');
|
||||
},
|
||||
}),
|
||||
subject: attr(),
|
||||
subtype_description: attr(),
|
||||
subtype_id: attr(),
|
||||
/**
|
||||
* All threads that this message is linked to. This field is read-only.
|
||||
*/
|
||||
threads: many('Thread', {
|
||||
compute() {
|
||||
const threads = [];
|
||||
if (this.isHistory && this.messaging.history) {
|
||||
threads.push(this.messaging.history.thread);
|
||||
}
|
||||
if (this.isNeedaction && this.messaging.inbox) {
|
||||
threads.push(this.messaging.inbox.thread);
|
||||
}
|
||||
if (this.isStarred && this.messaging.starred) {
|
||||
threads.push(this.messaging.starred.thread);
|
||||
}
|
||||
if (this.originThread) {
|
||||
threads.push(this.originThread);
|
||||
}
|
||||
return threads;
|
||||
},
|
||||
inverse: 'messages',
|
||||
}),
|
||||
trackingValues: many('TrackingValue', {
|
||||
inverse: 'messageOwner',
|
||||
isCausal: true,
|
||||
sort: [['smaller-first', 'id']],
|
||||
}),
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue