odoo.define('portal.chatter', function (require) { 'use strict'; var core = require('web.core'); const dom = require('web.dom'); var publicWidget = require('web.public.widget'); var time = require('web.time'); var portalComposer = require('portal.composer'); var qweb = core.qweb; var _t = core._t; /** * Widget PortalChatter * * - Fetch message fron controller * - Display chatter: pager, total message, composer (according to access right) * - Provider API to filter displayed messages */ var PortalChatter = publicWidget.Widget.extend({ template: 'portal.Chatter', xmlDependencies: ['/portal/static/src/xml/portal_chatter.xml'], events: { 'click .o_portal_chatter_pager_btn': '_onClickPager', 'click .o_portal_chatter_js_is_internal': 'async _onClickUpdateIsInternal', }, /** * @constructor */ init: function (parent, options) { var self = this; this.options = {}; this._super.apply(this, arguments); // underscorize the camelcased option keys _.each(options, function (val, key) { self.options[_.str.underscored(key)] = val; }); // set default options this.options = _.defaults(this.options, { 'allow_composer': true, 'display_composer': false, 'csrf_token': odoo.csrf_token, 'message_count': 0, 'pager_step': 10, 'pager_scope': 5, 'pager_start': 1, 'is_user_public': true, 'is_user_employee': false, 'is_user_publisher': false, 'hash': false, 'pid': false, 'domain': [], }); this.set('messages', []); this.set('message_count', this.options['message_count']); this.set('pager', {}); this.set('domain', this.options['domain']); this._currentPage = this.options['pager_start']; }, /** * @override */ willStart: function () { return Promise.all([ this._super.apply(this, arguments), this._chatterInit() ]); }, /** * @override */ start: function () { // bind events this.on("change:messages", this, this._renderMessages); this.on("change:message_count", this, function () { this._renderMessageCount(); this.set('pager', this._pager(this._currentPage)); }); this.on("change:pager", this, this._renderPager); this.on("change:domain", this, this._onChangeDomain); // set options and parameters this.set('message_count', this.options['message_count']); this.set('messages', this.preprocessMessages(this.result['messages'])); var defs = []; defs.push(this._super.apply(this, arguments)); // instanciate and insert composer widget if (this.options['display_composer']) { this._composer = new portalComposer.PortalComposer(this, this.options); defs.push(this._composer.replace(this.$('.o_portal_chatter_composer'))); } return Promise.all(defs); }, //-------------------------------------------------------------------------- // Public //-------------------------------------------------------------------------- /** * Fetch the messages and the message count from the server for the * current page and current domain. * * @param {Array} domain * @returns {Promise} */ messageFetch: function (domain) { var self = this; return this._rpc({ route: '/mail/chatter_fetch', params: self._messageFetchPrepareParams(), }).then(function (result) { self.set('messages', self.preprocessMessages(result['messages'])); self.set('message_count', result['message_count']); }); }, /** * Update the messages format * * @param {Array} * @returns {Array} */ preprocessMessages: function (messages) { _.each(messages, function (m) { m['author_avatar_url'] = _.str.sprintf('/web/image/%s/%s/author_avatar/50x50', 'mail.message', m.id); m['published_date_str'] = _.str.sprintf(_t('Published on %s'), moment(time.str_to_datetime(m.date)).format('MMMM Do YYYY, h:mm:ss a')); }); return messages; }, //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- /** * @private * @returns {Deferred} */ _chatterInit: function () { var self = this; return this._rpc({ route: '/mail/chatter_init', params: this._messageFetchPrepareParams() }).then(function (result) { self.result = result; self.options = _.extend(self.options, self.result['options'] || {}); return result; }); }, /** * Change the current page by refreshing current domain * * @private * @param {Number} page * @param {Array} domain */ _changeCurrentPage: function (page, domain) { this._currentPage = page; var d = domain ? domain : _.clone(this.get('domain')); this.set('domain', d); // trigger fetch message }, _messageFetchPrepareParams: function () { var self = this; var data = { 'res_model': this.options['res_model'], 'res_id': this.options['res_id'], 'limit': this.options['pager_step'], 'offset': (this._currentPage - 1) * this.options['pager_step'], 'allow_composer': this.options['allow_composer'], }; // add token field to allow to post comment without being logged if (self.options['token']) { data['token'] = self.options['token']; } // add domain if (this.get('domain')) { data['domain'] = this.get('domain'); } return data; }, /** * Generate the pager data for the given page number * * @private * @param {Number} page * @returns {Object} */ _pager: function (page) { page = page || 1; var total = this.get('message_count'); var scope = this.options['pager_scope']; var step = this.options['pager_step']; // Compute Pager var pageCount = Math.ceil(parseFloat(total) / step); page = Math.max(1, Math.min(parseInt(page), pageCount)); scope -= 1; var pmin = Math.max(page - parseInt(Math.floor(scope / 2)), 1); var pmax = Math.min(pmin + scope, pageCount); if (pmax - scope > 0) { pmin = pmax - scope; } else { pmin = 1; } var pages = []; _.each(_.range(pmin, pmax + 1), function (index) { pages.push(index); }); return { "page_count": pageCount, "offset": (page - 1) * step, "page": page, "page_start": pmin, "page_previous": Math.max(pmin, page - 1), "page_next": Math.min(pmax, page + 1), "page_end": pmax, "pages": pages }; }, _renderMessages: function () { this.$('.o_portal_chatter_messages').html(qweb.render("portal.chatter_messages", {widget: this})); }, _renderMessageCount: function () { this.$('.o_message_counter').replaceWith(qweb.render("portal.chatter_message_count", {widget: this})); }, _renderPager: function () { this.$('.o_portal_chatter_pager').replaceWith(qweb.render("portal.pager", {widget: this})); }, //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- _onChangeDomain: function () { var self = this; this.messageFetch().then(function () { var p = self._currentPage; self.set('pager', self._pager(p)); }); }, /** * @private * @param {MouseEvent} event */ _onClickPager: function (ev) { ev.preventDefault(); var page = $(ev.currentTarget).data('page'); this._changeCurrentPage(page); }, /** * Toggle is_internal state of message. Update both node data and * classes to ensure DOM is updated accordingly to RPC call result. * @private * @returns {Promise} */ _onClickUpdateIsInternal: function (ev) { ev.preventDefault(); var $elem = $(ev.currentTarget); return this._rpc({ route: '/mail/update_is_internal', params: { message_id: $elem.data('message-id'), is_internal: ! $elem.data('is-internal'), }, }).then(function (result) { $elem.data('is-internal', result); if (result === true) { $elem.addClass('o_portal_message_internal_on'); $elem.removeClass('o_portal_message_internal_off'); } else { $elem.addClass('o_portal_message_internal_off'); $elem.removeClass('o_portal_message_internal_on'); } }); }, }); publicWidget.registry.portalChatter = publicWidget.Widget.extend({ selector: '.o_portal_chatter', /** * @override */ async start() { const proms = [this._super.apply(this, arguments)]; const chatter = new PortalChatter(this, this.$el.data()); proms.push(chatter.appendTo(this.$el)); await Promise.all(proms); // scroll to the right place after chatter loaded if (window.location.hash === `#${this.el.id}`) { dom.scrollTo(this.el, {duration: 0}); } }, }); return { PortalChatter: PortalChatter, }; });