diff options
Diffstat (limited to 'addons/mail/static/src/js/models/threads/thread.js')
| -rw-r--r-- | addons/mail/static/src/js/models/threads/thread.js | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/addons/mail/static/src/js/models/threads/thread.js b/addons/mail/static/src/js/models/threads/thread.js new file mode 100644 index 00000000000..de2c6628d67 --- /dev/null +++ b/addons/mail/static/src/js/models/threads/thread.js @@ -0,0 +1,446 @@ +odoo.define('mail.model.Thread', function (require) { +"use strict"; + +var emojis = require('mail.emojis'); +var mailUtils = require('mail.utils'); + +var Class = require('web.Class'); +var Mixins = require('web.mixins'); +var ServicesMixin = require('web.ServicesMixin'); + +/** + * This is the super class modeling threads in JS. + * Such threads are stored in the mail manager, and any piece of JS code whose + * logic relies on threads must ideally interact with such objects. + * + * In particular, channels and mailboxes are two different kinds of threads. + */ +var Thread = Class.extend(Mixins.EventDispatcherMixin, ServicesMixin, { + /** + * @param {mail.Manager} parent + * @param {Object} data + * @param {string} [data.channel_type] + * @param {integer|string} data.id + * @param {string} data.name + * @param {string} [data.type] + */ + init: function (parent, data) { + Mixins.EventDispatcherMixin.init.call(this, arguments); + this.setParent(parent); + + this._id = data.id; + + // threads are not detached by default + this._detached = false; + // threads are unfolded by default + this._folded = false; + // if this._massMailing is set, display subject on messages, use + // extended composer and show "Send by messages by email" on discuss + // sidebar + this._massMailing = false; + this._name = data.name; + // on 1st request to getPreview, fetch data if incomplete. Otherwise it + // means that there is no message in this channel. + this._previewed = false; + this._type = data.type || data.channel_type; + // any new message that has not been read yet + this._unreadCounter = 0; + // max number of fetched messages from the server + this._FETCH_LIMIT = 30; + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Add a message to this thread. + * + * @abstract + * @param {mail.model.Message} message + */ + addMessage: function (message) {}, + /** + * Updates the _detached state of the thread. Must be overriden to reflect + * the new state in the interface. + */ + close: function () { + this._detached = false; + this._folded = false; + this._warnUpdatedWindowState(); + }, + /** + * Updates the _detached state of the thread. Must be overriden to reflect + * the new state in the interface. + * + * @param {Object} [options={}] + * @param {boolean} [options.keepFoldState=false] if set, keep the fold state + * of this thread. Otherwise unfold it while detaching it. + * @param {boolean} [options.passively=false] if set, if the thread window + * will be created passively. + */ + detach: function (options) { + options = options || {}; + this._detached = true; + this._folded = options.keepFoldState ? this._folded : false; + this._warnUpdatedWindowState({ + passively: options.passively, + }); + }, + /** + * Updates the _folded state of the thread. Must be overriden to reflect + * the new state in the interface. + */ + fold: function (folded) { + this._detached = true; + this._folded = folded; + this._warnUpdatedWindowState(); + }, + /** + * Get the list of available commands for the thread. + * By default, threads do not have any available command. + * + * @returns {Array} + */ + getCommands: function () { + return []; + }, + /** + * Get the ID of the thread + * + * @returns {integer|string} string for mailboxes (e.g. 'mailbox_inbox') + */ + getID: function () { + return this._id; + }, + /** + * Get the listeners of the thread. + * By default, a thread has not listener. + * + * @abstract + * @returns {$.Promise<Object[]>} + */ + getMentionPartnerSuggestions: function () { + return $.when([]); + }, + /** + * Get the list of messages in this thread. + * By default, a thread has no messages. + * + * Note that this method only returns some messages, as we do not want to + * fetch all messages of a thread at once, just to read the last message. + * + * As a result, this method fetches only some messages of the thread, + * starting from the last message in the thread. At most, it fetches + * `this.LIMIT` number of messages at a time. + * + * @abstract + * @returns {$.Promise<mail.model.Message[]>} + */ + getMessages: function () { + return $.when([]); + }, + /** + * Get the name of this thread + * + * @returns {string|Object} object for lazy translated names with _lt + */ + getName: function () { + return this._name; + }, + /** + * Returns the information required to render the preview of this channel. + * + * @returns {Object} a valid object for the rendering of previews + * (@see mail.Preview template) + */ + getPreview: function () { + return { + id: this.getID(), + imageSRC: '/mail/static/src/img/smiley/avatar.jpg', + isChat: this.isChat(), + status: this.getStatus(), + title: this.getName(), + unreadCounter: this.getUnreadCounter(), + }; + }, + /** + * Get the status of the thread (e.g. 'online', 'offline', etc.) + * + * @returns {string} + */ + getStatus: function () { + return this._status; + }, + /** + * Returns the title to display in thread window's headers. + * + * @returns {string|Object} the name of the thread by default (see @getName) + */ + getTitle: function () { + return this.getName(); + }, + /** + * @returns {string} + */ + getType: function () { + return this._type; + }, + /** + * @returns {integer} + */ + getUnreadCounter: function () { + return this._unreadCounter; + }, + /** + * State whether this channel has been previewed + * + * A channel that has been previewed means that it had the necessary data + * to display its preview format. A channel needs its meta data and the + * last message in order to build its preview format. + * + * This is useful in order to not fetch preview info on this channel more + * than once on channels that have no message at all. + * + * Any received message updates the last_message, so a channel should + * always have all the necessary information to display its preview after + * the 1st time. + * + * @returns {boolean} + */ + hasBeenPreviewed: function () { + return this._previewed; + }, + /** + * State whether there are unread messages in this thread + * + * @returns {boolean} + */ + hasUnreadMessages: function () { + return this._unreadCounter !== 0; + }, + /** + * Increments the needaction counter of this thread + * FIXME: this method makes only sense for channels. Not sure, but I think + * this method is necessary at this level because of the model + * im_support.SupportChannel + * + * @abstract + */ + incrementNeedactionCounter: function () {}, + /** + * Increment the unread counter of this thread by 1 unit + */ + incrementUnreadCounter: function () { + this._unreadCounter++; + this._warnNewUnreadCounter(); + }, + /** + * States whether the thread should be auto-selected on creation + * + * Note that this is not of the responsibility of the thread: it only + * provides guidance for the object that uses threads (e.g. mail.Discuss + * must listen on threads and auto-select the thread if autoswitch is set). + * + * By default, threads are not in autoswitch mode. + * + * @returns {boolean} + */ + isAutoswitch: function () { + return false; + }, + /** + * States whether this thread is a channel or not. A thread is a channel if + * it is an instance of mail.model.Channel (direct or indirect). + * By default, any thread is not a channel + * + * @returns {boolean} + */ + isChannel: function () { + return false; + }, + /** + * States whether this thread is a chat or not. In particular, public and + * private channels are not chat, but DMs and Livechats are chats. Chats + * are threads used for communication between two users. + * By default, any thread is not a chat + * + * @returns {boolean} + */ + isChat: function () { + return false; + }, + /** + * States whether this thread is detached or not. + * A thread that is detached must have a thread window linked to itself. + * + * @return {boolean} + */ + isDetached: function () { + return this._detached; + }, + /** + * States whether this thread is folded or not. + * + * @return {boolean} + */ + isFolded: function () { + return this._folded; + }, + /** + * States whether the thread is linked to a document + * By default, threads are not linked to a document. + * + * @returns {boolean} + */ + isLinkedToDocument: function () { + return false; + }, + /** + * States whether this thread has the mass mailing setting active or not. + * This is a server-side setting, that determine the type of composer that + * is used (basic or extended composer). + * + * @return {boolean} + */ + isMassMailing: function () { + return this._massMailing; + }, + /** + + * States whether the thread is moderated or not. + * By default, threads are not moderated. + * + * @returns {boolean} + */ + isModerated: function () { + return false; + }, + /** + * States whether the current user is moderator of this thread. + * By default, the current user is not moderator of this thread. + * + * @returns {boolean} + */ + isModerator: function () { + return false; + }, + /** + * Mark this channel as previewed + * + * This is useful in order to not fetch preview info on this channel + * is the server has no preview in the first place. + * + * Note: preview fetch is useful only when the channel contains messages + * that have not been fetched at all. After that, this channel instance + * is updated regularly so that the most up-to-date info are available + * to make the preview of this channel. + */ + markAsPreviewed: function () { + this._previewed = true; + }, + /** + * Mark the thread as read, which resets the unread counter to 0. + * + * @returns {$.Promise} resolved + */ + markAsRead: function () { + if (this._unreadCounter > 0) { + this._unreadCounter = 0; + this._warnNewUnreadCounter(); + } + return $.when(); + }, + /** + * Post a message on the thread. + * + * This method must be completed by concrete threads, + * As it currently only pre-process the messages at the moment. + * + * @abstract + * @param {Object} data + * @returns {$.Promise<Object>} resolved with the message object to be sent + * to the server + */ + postMessage: function (data) { + // This message will be received from the mail composer as html content + // subtype but the urls will not be linkified. If the mail composer + // takes the responsibility to linkify the urls we end up with double + // linkification a bit everywhere. Ideally we want to keep the content + // as text internally and only make html enrichment at display time but + // the current design makes this quite hard to do. + var body = mailUtils.parseAndTransform(_.str.trim(data.content), mailUtils.addLink); + body = this._generateEmojis(body); + var messageData = { + partner_ids: data.partner_ids, + body: body, + attachment_ids: data.attachment_ids, + }; + if ('subject' in data) { + messageData.subject = data.subject; + } + return $.when(messageData); + }, + /** + * @private + */ + resetUnreadCounter: function () { + this._unreadCounter = 0; + this._warnNewUnreadCounter(); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @abstract + * @private + * @return {$.Promise} + */ + _fetchMessages: function () { + return $.when(); + }, + /** + * Replace character representations of emojis by their unicode + * representation in the provided HTML string. Note that the provided html + * string is altered by this function. + * + * @param {string} htmlString + * @returns {string} + */ + _generateEmojis: function (htmlString) { + _.each(emojis, function (emoji) { + _.each(emoji.sources, function (source) { + var escapedSource = String(_.escape(source)).replace(/([.*+?=^!:${}()|[\]/\\])/g, '\\$1'); + var regexp = new RegExp("(\\s|^)(" + escapedSource + ")(?=\\s|$)", 'g'); + htmlString = htmlString.replace(regexp, '$1' + emoji.unicode); + }); + }); + return htmlString; + }, + /** + * Warn other mail components that the window state of this thread has + * changed (it has become closed, folded, unfolded, detached, etc.) + * + * @private + * @param {Object} [options={}] + */ + _warnUpdatedWindowState: function (options) { + options = options || {}; + this.call('mail_service', 'updateThreadWindow', this.getID(), options); + }, + /** + * Warn on the chat bus that the unread counter has been updated + * + * @private + */ + _warnNewUnreadCounter: function () { + this.call('mail_service', 'getMailBus') + .trigger('update_thread_unread_counter', this); + }, +}); + +return Thread; + +}); |
