diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/mail/static/src/models/discuss | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/mail/static/src/models/discuss')
| -rw-r--r-- | addons/mail/static/src/models/discuss/discuss.js | 568 |
1 files changed, 568 insertions, 0 deletions
diff --git a/addons/mail/static/src/models/discuss/discuss.js b/addons/mail/static/src/models/discuss/discuss.js new file mode 100644 index 00000000..513b77fd --- /dev/null +++ b/addons/mail/static/src/models/discuss/discuss.js @@ -0,0 +1,568 @@ +odoo.define('mail/static/src/models/discuss.discuss.js', function (require) { +'use strict'; + +const { registerNewModel } = require('mail/static/src/model/model_core.js'); +const { attr, many2one, one2many, one2one } = require('mail/static/src/model/model_field.js'); +const { clear } = require('mail/static/src/model/model_field_command.js'); + +function factory(dependencies) { + + class Discuss extends dependencies['mail.model'] { + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + /** + * @param {mail.thread} thread + */ + cancelThreadRenaming(thread) { + this.update({ renamingThreads: [['unlink', thread]] }); + } + + clearIsAddingItem() { + this.update({ + addingChannelValue: "", + isAddingChannel: false, + isAddingChat: false, + }); + } + + clearReplyingToMessage() { + this.update({ replyingToMessage: [['unlink-all']] }); + } + + /** + * Close the discuss app. Should reset its internal state. + */ + close() { + this.update({ isOpen: false }); + } + + focus() { + this.update({ isDoFocus: true }); + } + + /** + * @param {Event} ev + * @param {Object} ui + * @param {Object} ui.item + * @param {integer} ui.item.id + */ + async handleAddChannelAutocompleteSelect(ev, ui) { + const name = this.addingChannelValue; + this.clearIsAddingItem(); + if (ui.item.special) { + const channel = await this.async(() => + this.env.models['mail.thread'].performRpcCreateChannel({ + name, + privacy: ui.item.special, + }) + ); + channel.open(); + } else { + const channel = await this.async(() => + this.env.models['mail.thread'].performRpcJoinChannel({ + channelId: ui.item.id, + }) + ); + channel.open(); + } + } + + /** + * @param {Object} req + * @param {string} req.term + * @param {function} res + */ + async handleAddChannelAutocompleteSource(req, res) { + const value = req.term; + const escapedValue = owl.utils.escape(value); + this.update({ addingChannelValue: value }); + const domain = [ + ['channel_type', '=', 'channel'], + ['name', 'ilike', value], + ]; + const fields = ['channel_type', 'name', 'public', 'uuid']; + const result = await this.async(() => this.env.services.rpc({ + model: "mail.channel", + method: "search_read", + kwargs: { + domain, + fields, + }, + })); + const items = result.map(data => { + let escapedName = owl.utils.escape(data.name); + return Object.assign(data, { + label: escapedName, + value: escapedName + }); + }); + // XDU FIXME could use a component but be careful with owl's + // renderToString https://github.com/odoo/owl/issues/708 + items.push({ + label: _.str.sprintf( + `<strong>${this.env._t('Create %s')}</strong>`, + `<em><span class="fa fa-hashtag"/>${escapedValue}</em>`, + ), + escapedValue, + special: 'public' + }, { + label: _.str.sprintf( + `<strong>${this.env._t('Create %s')}</strong>`, + `<em><span class="fa fa-lock"/>${escapedValue}</em>`, + ), + escapedValue, + special: 'private' + }); + res(items); + } + + /** + * @param {Event} ev + * @param {Object} ui + * @param {Object} ui.item + * @param {integer} ui.item.id + */ + handleAddChatAutocompleteSelect(ev, ui) { + this.env.messaging.openChat({ partnerId: ui.item.id }); + this.clearIsAddingItem(); + } + + /** + * @param {Object} req + * @param {string} req.term + * @param {function} res + */ + handleAddChatAutocompleteSource(req, res) { + const value = owl.utils.escape(req.term); + this.env.models['mail.partner'].imSearch({ + callback: partners => { + const suggestions = partners.map(partner => { + return { + id: partner.id, + value: partner.nameOrDisplayName, + label: partner.nameOrDisplayName, + }; + }); + res(_.sortBy(suggestions, 'label')); + }, + keyword: value, + limit: 10, + }); + } + + /** + * Open thread from init active id. `initActiveId` is used to refer to + * a thread that we may not have full data yet, such as when messaging + * is not yet initialized. + */ + openInitThread() { + const [model, id] = typeof this.initActiveId === 'number' + ? ['mail.channel', this.initActiveId] + : this.initActiveId.split('_'); + const thread = this.env.models['mail.thread'].findFromIdentifyingData({ + id: model !== 'mail.box' ? Number(id) : id, + model, + }); + if (!thread) { + return; + } + thread.open(); + if (this.env.messaging.device.isMobile && thread.channel_type) { + this.update({ activeMobileNavbarTabId: thread.channel_type }); + } + } + + + /** + * Opens the given thread in Discuss, and opens Discuss if necessary. + * + * @param {mail.thread} thread + */ + async openThread(thread) { + this.update({ + thread: [['link', thread]], + }); + this.focus(); + if (!this.isOpen) { + this.env.bus.trigger('do-action', { + action: 'mail.action_discuss', + options: { + active_id: this.threadToActiveId(this), + clear_breadcrumbs: false, + on_reverse_breadcrumb: () => this.close(), + }, + }); + } + } + + /** + * @param {mail.thread} thread + * @param {string} newName + */ + async renameThread(thread, newName) { + await this.async(() => thread.rename(newName)); + this.update({ renamingThreads: [['unlink', thread]] }); + } + + /** + * Action to initiate reply to given message in Inbox. Assumes that + * Discuss and Inbox are already opened. + * + * @param {mail.message} message + */ + replyToMessage(message) { + this.update({ replyingToMessage: [['link', message]] }); + // avoid to reply to a note by a message and vice-versa. + // subject to change later by allowing subtype choice. + this.replyingToMessageOriginThreadComposer.update({ + isLog: !message.is_discussion && !message.is_notification + }); + this.focus(); + } + + /** + * @param {mail.thread} thread + */ + setThreadRenaming(thread) { + this.update({ renamingThreads: [['link', thread]] }); + } + + /** + * @param {mail.thread} thread + * @returns {string} + */ + threadToActiveId(thread) { + return `${thread.model}_${thread.id}`; + } + + //---------------------------------------------------------------------- + // Private + //---------------------------------------------------------------------- + + /** + * @private + * @returns {string|undefined} + */ + _computeActiveId() { + if (!this.thread) { + return clear(); + } + return this.threadToActiveId(this.thread); + } + + /** + * @private + * @returns {string} + */ + _computeAddingChannelValue() { + if (!this.isOpen) { + return ""; + } + return this.addingChannelValue; + } + + /** + * @private + * @returns {boolean} + */ + _computeHasThreadView() { + if (!this.thread || !this.isOpen) { + return false; + } + if ( + this.env.messaging.device.isMobile && + ( + this.activeMobileNavbarTabId !== 'mailbox' || + this.thread.model !== 'mail.box' + ) + ) { + return false; + } + return true; + } + + /** + * @private + * @returns {boolean} + */ + _computeIsAddingChannel() { + if (!this.isOpen) { + return false; + } + return this.isAddingChannel; + } + + /** + * @private + * @returns {boolean} + */ + _computeIsAddingChat() { + if (!this.isOpen) { + return false; + } + return this.isAddingChat; + } + + /** + * @private + * @returns {boolean} + */ + _computeIsReplyingToMessage() { + return !!this.replyingToMessage; + } + + /** + * Ensures the reply feature is disabled if discuss is not open. + * + * @private + * @returns {mail.message|undefined} + */ + _computeReplyingToMessage() { + if (!this.isOpen) { + return [['unlink-all']]; + } + return []; + } + + + /** + * Only pinned threads are allowed in discuss. + * + * @private + * @returns {mail.thread|undefined} + */ + _computeThread() { + let thread = this.thread; + if (this.env.messaging && + this.env.messaging.inbox && + this.env.messaging.device.isMobile && + this.activeMobileNavbarTabId === 'mailbox' && + this.initActiveId !== 'mail.box_inbox' && + !thread + ) { + // After loading Discuss from an arbitrary tab other then 'mailbox', + // switching to 'mailbox' requires to also set its inner-tab ; + // by default the 'inbox'. + return [['replace', this.env.messaging.inbox]]; + } + if (!thread || !thread.isPinned) { + return [['unlink']]; + } + return []; + } + + } + + Discuss.fields = { + activeId: attr({ + compute: '_computeActiveId', + dependencies: [ + 'thread', + 'threadId', + 'threadModel', + ], + }), + /** + * Active mobile navbar tab, either 'mailbox', 'chat', or 'channel'. + */ + activeMobileNavbarTabId: attr({ + default: 'mailbox', + }), + /** + * Value that is used to create a channel from the sidebar. + */ + addingChannelValue: attr({ + compute: '_computeAddingChannelValue', + default: "", + dependencies: ['isOpen'], + }), + /** + * Serves as compute dependency. + */ + device: one2one('mail.device', { + related: 'messaging.device', + }), + /** + * Serves as compute dependency. + */ + deviceIsMobile: attr({ + related: 'device.isMobile', + }), + /** + * Determine if the moderation discard dialog is displayed. + */ + hasModerationDiscardDialog: attr({ + default: false, + }), + /** + * Determine if the moderation reject dialog is displayed. + */ + hasModerationRejectDialog: attr({ + default: false, + }), + /** + * Determines whether `this.thread` should be displayed. + */ + hasThreadView: attr({ + compute: '_computeHasThreadView', + dependencies: [ + 'activeMobileNavbarTabId', + 'deviceIsMobile', + 'isOpen', + 'thread', + 'threadModel', + ], + }), + /** + * Formatted init thread on opening discuss for the first time, + * when no active thread is defined. Useful to set a thread to + * open without knowing its local id in advance. + * Support two formats: + * {string} <threadModel>_<threadId> + * {int} <channelId> with default model of 'mail.channel' + */ + initActiveId: attr({ + default: 'mail.box_inbox', + }), + /** + * Determine whether current user is currently adding a channel from + * the sidebar. + */ + isAddingChannel: attr({ + compute: '_computeIsAddingChannel', + default: false, + dependencies: ['isOpen'], + }), + /** + * Determine whether current user is currently adding a chat from + * the sidebar. + */ + isAddingChat: attr({ + compute: '_computeIsAddingChat', + default: false, + dependencies: ['isOpen'], + }), + /** + * Determine whether this discuss should be focused at next render. + */ + isDoFocus: attr({ + default: false, + }), + /** + * Whether the discuss app is open or not. Useful to determine + * whether the discuss or chat window logic should be applied. + */ + isOpen: attr({ + default: false, + }), + isReplyingToMessage: attr({ + compute: '_computeIsReplyingToMessage', + default: false, + dependencies: ['replyingToMessage'], + }), + isThreadPinned: attr({ + related: 'thread.isPinned', + }), + /** + * The menu_id of discuss app, received on mail/init_messaging and + * used to open discuss from elsewhere. + */ + menu_id: attr({ + default: null, + }), + messaging: one2one('mail.messaging', { + inverse: 'discuss', + }), + messagingInbox: many2one('mail.thread', { + related: 'messaging.inbox', + }), + renamingThreads: one2many('mail.thread'), + /** + * The message that is currently selected as being replied to in Inbox. + * There is only one reply composer shown at a time, which depends on + * this selected message. + */ + replyingToMessage: many2one('mail.message', { + compute: '_computeReplyingToMessage', + dependencies: [ + 'isOpen', + 'replyingToMessage', + ], + }), + /** + * The thread concerned by the reply feature in Inbox. It depends on the + * message set to be replied, and should be considered read-only. + */ + replyingToMessageOriginThread: many2one('mail.thread', { + related: 'replyingToMessage.originThread', + }), + /** + * The composer to display for the reply feature in Inbox. It depends + * on the message set to be replied, and should be considered read-only. + */ + replyingToMessageOriginThreadComposer: one2one('mail.composer', { + inverse: 'discussAsReplying', + related: 'replyingToMessageOriginThread.composer', + }), + /** + * Quick search input value in the discuss sidebar (desktop). Useful + * to filter channels and chats based on this input content. + */ + sidebarQuickSearchValue: attr({ + default: "", + }), + /** + * Determines the domain to apply when fetching messages for `this.thread`. + * This value should only be written by the control panel. + */ + stringifiedDomain: attr({ + default: '[]', + }), + /** + * Determines the `mail.thread` that should be displayed by `this`. + */ + thread: many2one('mail.thread', { + compute: '_computeThread', + dependencies: [ + 'activeMobileNavbarTabId', + 'deviceIsMobile', + 'isThreadPinned', + 'messaging', + 'messagingInbox', + 'thread', + 'threadModel', + ], + }), + threadId: attr({ + related: 'thread.id', + }), + threadModel: attr({ + related: 'thread.model', + }), + /** + * States the `mail.thread_view` displaying `this.thread`. + */ + threadView: one2one('mail.thread_view', { + related: 'threadViewer.threadView', + }), + /** + * Determines the `mail.thread_viewer` managing the display of `this.thread`. + */ + threadViewer: one2one('mail.thread_viewer', { + default: [['create']], + inverse: 'discuss', + isCausal: true, + }), + }; + + Discuss.modelName = 'mail.discuss'; + + return Discuss; +} + +registerNewModel('mail.discuss', factory); + +}); |
