diff options
| author | Sébastien Theys <seb@odoo.com> | 2020-08-28 12:53:38 +0000 |
|---|---|---|
| committer | Sébastien Theys <seb@odoo.com> | 2020-08-28 16:15:15 +0000 |
| commit | 17611f4adc5e950b0a75a2a6615b1da3c956387f (patch) | |
| tree | ede6cbc0bc67c1dceed3b5ca6c1bb3e08389675c /addons/mail/static/src/models/thread/thread.js | |
| parent | 5ba64925fd846e9a9a4884913e3d1004d05ecdf6 (diff) | |
[FIX] mail, *: fix opens and redirects, adapt messaging menu close
task-2244279
task-2262862
task-2274993
task-2277537
task-2282238
closes odoo/odoo#56745
X-original-commit: 109f202
Related: odoo/enterprise#12761
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
Diffstat (limited to 'addons/mail/static/src/models/thread/thread.js')
| -rw-r--r-- | addons/mail/static/src/models/thread/thread.js | 316 |
1 files changed, 162 insertions, 154 deletions
diff --git a/addons/mail/static/src/models/thread/thread.js b/addons/mail/static/src/models/thread/thread.js index d34d295223a..efd33f9a0ce 100644 --- a/addons/mail/static/src/models/thread/thread.js +++ b/addons/mail/static/src/models/thread/thread.js @@ -133,6 +133,9 @@ function factory(dependencies) { */ static convertData(data) { const data2 = {}; + if ('model' in data) { + data2.model = data.model; + } if ('channel_type' in data) { data2.channel_type = data.channel_type; data2.model = 'mail.channel'; @@ -249,103 +252,140 @@ function factory(dependencies) { } /** - * Create a channel, which is a special kind of thread on model - * 'mail.channel' with multiple members. + * Load the previews of the specified threads. Basically, it fetches the + * last messages, since they are used to display inline content of them. + * + * @static + * @param {mail.thread[]} threads + */ + static async loadPreviews(threads) { + const channelIds = threads.reduce((list, thread) => { + if (thread.model === 'mail.channel') { + return list.concat(thread.id); + } + return list; + }, []); + const channelPreviews = await this.env.services.rpc({ + model: 'mail.channel', + method: 'channel_fetch_preview', + args: [channelIds], + }, { shadow: true }); + this.env.models['mail.message'].insert(channelPreviews.filter(p => p.last_message).map( + channelPreview => this.env.models['mail.message'].convertData(channelPreview.last_message) + )); + } + + /** + * Performs the `channel_info` RPC on `mail.channel`. + * + * @static + * @param {Object} param0 + * @param {integer[]} param0.ids list of id of channels + * @returns {mail.thread[]} + */ + static async performRpcChannelInfo({ ids }) { + const channelInfos = await this.env.services.rpc({ + model: 'mail.channel', + method: 'channel_info', + args: [ids], + }); + const channels = this.env.models['mail.thread'].insert( + channelInfos.map(channelInfo => this.env.models['mail.thread'].convertData(channelInfo)) + ); + // manually force recompute of counter + this.env.messaging.messagingMenu.update(); + return channels; + } + + /** + * Performs the `channel_create` RPC on `mail.channel`. * * @static * @param {Object} param0 - * @param {boolean} [param0.autoselect=false] if set, when channel - * has been created, it auto-open the channel. This opens in discuss - * or chat window, depending on whether discuss is open or not. - * @param {string} [param0.autoselectChatWindowMode] * @param {string} param0.name - * @param {integer} [param0.partnerId] - * @param {string} [param0.public] - * @param {string} param0.type - */ - static async createChannel({ - autoselect = false, - autoselectChatWindowMode, - name, - partnerId, - public: publicStatus, - type, - }) { + * @param {string} [param0.privacy] + * @returns {mail.thread} the created channel + */ + static async performRpcCreateChannel({ name, privacy }) { const device = this.env.messaging.device; - // TODO FIX: potential duplicate chat task-2276490 const data = await this.env.services.rpc({ model: 'mail.channel', - method: type === 'chat' ? 'channel_get' : 'channel_create', - args: type === 'chat' ? [[partnerId]] : [name, publicStatus], + method: 'channel_create', + args: [name, privacy], kwargs: { context: Object.assign({}, this.env.session.user_content, { + // optimize the return value by avoiding useless queries + // in non-mobile devices isMobile: device.isMobile, }), - } + }, }); - const thread = this.insert(this.convertData(data)); - if (autoselect) { - thread.open({ chatWindowMode: autoselectChatWindowMode }); - } + return this.env.models['mail.thread'].insert( + this.env.models['mail.thread'].convertData(data) + ); } /** - * Join a channel. This channel may not yet exists in the store. + * Performs the `channel_get` RPC on `mail.channel`. + * + * `openChat` is preferable in business code because it will avoid the + * RPC if the chat already exists. * * @static - * @param {integer} channelId - * @param {Object} [param1={}] - * @param {boolean} [param1.autoselect=false] - */ - static async joinChannel(channelId, { autoselect = false } = {}) { - let channel = this.find(thread => - thread.id === channelId && - thread.model === 'mail.channel' - ); - if (channel && channel.isPinned) { - return; - } + * @param {Object} param0 + * @param {integer[]} param0.partnerIds + * @param {boolean} [param0.pinForCurrentPartner] + * @returns {mail.thread|undefined} the created or existing chat + */ + static async performRpcCreateChat({ partnerIds, pinForCurrentPartner }) { + const device = this.env.messaging.device; + // TODO FIX: potential duplicate chat task-2276490 const data = await this.env.services.rpc({ model: 'mail.channel', - method: 'channel_join_and_get_info', - args: [[channelId]] + method: 'channel_get', + kwargs: { + context: Object.assign({}, this.env.session.user_content, { + // optimize the return value by avoiding useless queries + // in non-mobile devices + isMobile: device.isMobile, + }), + partners_to: partnerIds, + pin: pinForCurrentPartner, + }, }); - // We just joined the channel because of the previous rpc - // the main assumption here is that we didn't have the channel - // in memory. If we did though, clear the pending state and - // let the server's data be the master - const convertedData = Object.assign( - { isPendingPinned: undefined }, - this.convertData(data), - ); - channel = this.insert(convertedData); - if (autoselect) { - channel.open({ resetDiscussDomain: true }); + if (!data) { + return; } + return this.env.models['mail.thread'].insert( + this.env.models['mail.thread'].convertData(data) + ); } /** - * Load the previews of the specified threads. Basically, it fetches the - * last messages, since they are used to display inline content of them. + * Performs the `channel_join_and_get_info` RPC on `mail.channel`. * * @static - * @param {mail.thread[]} threads + * @param {Object} param0 + * @param {integer} param0.channelId + * @returns {mail.thread} the channel that was joined */ - static async loadPreviews(threads) { - const channelIds = threads.reduce((list, thread) => { - if (thread.model === 'mail.channel') { - return list.concat(thread.id); - } - return list; - }, []); - const channelPreviews = await this.env.services.rpc({ + static async performRpcJoinChannel({ channelId }) { + const device = this.env.messaging.device; + const data = await this.env.services.rpc({ model: 'mail.channel', - method: 'channel_fetch_preview', - args: [channelIds], - }, { shadow: true }); - this.env.models['mail.message'].insert(channelPreviews.filter(p => p.last_message).map( - channelPreview => this.env.models['mail.message'].convertData(channelPreview.last_message) - )); + method: 'channel_join_and_get_info', + args: [[channelId]], + kwargs: { + context: Object.assign({}, this.env.session.user_content, { + // optimize the return value by avoiding useless queries + // in non-mobile devices + isMobile: device.isMobile, + }), + }, + }); + return this.env.models['mail.thread'].insert( + this.env.models['mail.thread'].convertData(data) + ); } /** @@ -453,7 +493,7 @@ function factory(dependencies) { * * Only makes sense if pendingFoldState is set to the desired value. */ - async notifyFoldStateToServer() { + notifyFoldStateToServer() { // method is called from _updateAfter so it cannot be async this.env.services.rpc({ model: 'mail.channel', @@ -469,75 +509,67 @@ function factory(dependencies) { * Notify server to leave the current channel. Useful for cross-tab * and cross-device chat window state synchronization. * - * Only makes sense if pendingServerState is set to 'unpin'. + * Only makes sense if isPendingPinned is set to the desired value. */ - notifyUnPinToServer() { + notifyPinStateToServer() { // method is called from _updateAfter so it cannot be async - this.env.services.rpc({ - model: 'mail.channel', - method: 'execute_command', - args: [[this.id], 'leave'] - }); + if (this.isPendingPinned) { + this.env.services.rpc({ + model: 'mail.channel', + method: 'channel_pin', + kwargs: { + uuid: this.uuid, + pinned: true, + }, + }, { shadow: true }); + } else { + this.env.services.rpc({ + model: 'mail.channel', + method: 'execute_command', + args: [[this.id], 'leave'] + }, { shadow: true }); + } } /** - * Open provided thread, either in discuss app or as a chat window. + * Opens this thread either as form view, in discuss app, or as a chat + * window. The thread will be opened in an "active" matter, which will + * interrupt current user flow. * - * @param {Object} param0 - * @param {string} [param0.chatWindowMode='last_visible'] - * @param {boolean} [param0.resetDiscussDomain=false] + * @param {Object} [param0] + * @param {boolean} [param0.expanded=false] */ - open({ chatWindowMode = 'last_visible', resetDiscussDomain = false } = {}) { - const device = this.env.messaging.device; - const discuss = this.env.messaging.discuss; - const messagingMenu = this.env.messaging.messagingMenu; + async open({ expanded = false } = {}) { + // check if thread must be opened in form view if (!['mail.box', 'mail.channel'].includes(this.model)) { - this.env.messaging.openDocument({ + return this.env.messaging.openDocument({ id: this.id, model: this.model, }); } + // check if thread must be opened in discuss + const device = this.env.messaging.device; + const discuss = this.env.messaging.discuss; if ( - (!device.isMobile && discuss.isOpen) || - (device.isMobile && this.model === 'mail.box') + (!device.isMobile && (discuss.isOpen || expanded)) || + this.model === 'mail.box' ) { - if (resetDiscussDomain) { - discuss.threadView.update({ stringifiedDomain: '[]' }); - } - discuss.threadView.update({ thread: [['link', this]] }); - } else { - this.env.messaging.chatWindowManager.openThread(this, { mode: chatWindowMode }); - } - if (!device.isMobile) { - messagingMenu.close(); + return discuss.openThread(this); } + // thread must be opened in chat window + return this.env.messaging.chatWindowManager.openThread(this, { + makeActive: true, + }); } /** - * Open this thread in an expanded way, that is not in a chat window. + * Opens the most appropriate view that is a profile for this thread. */ - openExpanded() { - const discuss = this.env.messaging.discuss; - if (['mail.channel', 'mail.box'].includes(this.model)) { - discuss.threadView.update({ thread: [['link', this]] }); - this.env.bus.trigger('do-action', { - action: 'mail.action_discuss', - options: { - clear_breadcrumbs: false, - active_id: discuss.threadToActiveId(this), - on_reverse_breadcrumb: () => discuss.close(), - }, - }); - } else { - this.env.bus.trigger('do-action', { - action: { - type: 'ir.actions.act_window', - res_model: this.model, - views: [[false, 'form']], - res_id: this.id, - }, - }); - } + async openProfile() { + return this.env.messaging.openDocument({ + id: this.id, + model: this.model, + }); } /** @@ -760,15 +792,6 @@ function factory(dependencies) { /** * @private - * @returns {mail.chat_window[]} - */ - _computeChatWindows() { - const chatWindowThreadViews = this.threadViews.filter(threadView => !!threadView.chatWindow); - return [['replace', chatWindowThreadViews.map(threadView => threadView.chatWindow)]]; - } - - /** - * @private * @returns {mail.partner} */ _computeCorrespondent() { @@ -1088,10 +1111,10 @@ function factory(dependencies) { this.update({ pendingFoldState: undefined }); } if ( - this.isPendingPinned === false && + this.isPendingPinned !== undefined && previous.isPendingPinned !== this.isPendingPinned ) { - this.notifyUnPinToServer(); + this.notifyPinStateToServer(); } if ( this.isServerPinned === this.isPendingPinned @@ -1110,16 +1133,14 @@ function factory(dependencies) { // the process return; } - if (this.foldState !== 'closed' && this.chatWindows.length === 0) { - // condition to avoid crash during destroy - if (this.env.messaging.chatWindowManager) { - this.env.messaging.chatWindowManager.openThread(this); - } + if (!this.env.messaging.chatWindowManager) { + // avoid crash during destroy + return; } - if (this.foldState === 'closed' && this.chatWindows.length > 0) { - for (const chatWindow of this.chatWindows) { - chatWindow.close(); - } + if (this.foldState !== 'closed') { + this.env.messaging.chatWindowManager.openThread(this); + } else { + this.env.messaging.chatWindowManager.closeThread(this); } } @@ -1192,18 +1213,8 @@ function factory(dependencies) { isCausal: true, }), channel_type: attr(), - chatWindows: one2many('mail.chat_window', { - compute: '_computeChatWindows', - dependencies: ['threadViewsChatWindow'], - }), composer: one2one('mail.composer', { - /** - * FIXME With this, whenever client is aware of new thread, this will - * (almost) always focus its composer when displayed. This shouldn't be - * the case, instead auto-focus of composer is flow-specific. - * See task-2277537 - */ - default: [['create', { isDoFocus: true }]], + default: [['create']], inverse: 'thread', isCausal: true, }), @@ -1475,9 +1486,6 @@ function factory(dependencies) { threadViews: one2many('mail.thread_view', { inverse: 'thread', }), - threadViewsChatWindow: many2many('mail.chat_window', { - related: 'threadViews.chatWindow', - }), }; Thread.modelName = 'mail.thread'; |
