summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/js/composers/basic_composer.js
diff options
context:
space:
mode:
authorSébastien Theys <seb@odoo.com>2020-06-15 18:12:21 +0000
committerSébastien Theys <seb@odoo.com>2020-06-15 18:12:21 +0000
commit3fea5b2136627c1d7e83b2a5e111952fe3eb10a1 (patch)
tree5eefad07972a87ce4689b83260f981bc87aadad4 /addons/mail/static/src/js/composers/basic_composer.js
parent0a82848606151ef60eb6523e9c4c161171f214e7 (diff)
[REF] mail, *: refactor messaging with OWL
This commit is a significant rewriting of client-side discuss, chatter, chat window, and messaging menu using OWL. The behavior should be broadly the same, with some slight functional changes here and there. From a technical standpoint, the code of messaging is mainly organized in 2 main groups of modules: - models, which are logical entities that depict the client-side state of messaging as a whole. - components, which are in charge of displaying information from models. This refactoring also introduces new JS guidelines regarding folder structure (/static) and naming rules for JS modules. Community PR: https://github.com/odoo/odoo/pull/39023 Enterprise PR: https://github.com/odoo/enterprise/pull/6249 Task-1914207 This PR is a collaborative work by Alexandre, Julien, Sébastien and Xavier, with the precious help of Lucas to speed it up towards the end. closes odoo/odoo#39023 Related: odoo/enterprise#6249 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com> Co-authored-by: Alexandre Kühn <aku@odoo.com> Co-authored-by: Julien Giannone <jgi@odoo.com> Co-authored-by: Lucas Perais <lpe@odoo.com> Co-authored-by: Sébastien Theys <seb@odoo.com> Co-authored-by: Xavier Dubuc <xdu@odoo.com>
Diffstat (limited to 'addons/mail/static/src/js/composers/basic_composer.js')
-rw-r--r--addons/mail/static/src/js/composers/basic_composer.js864
1 files changed, 0 insertions, 864 deletions
diff --git a/addons/mail/static/src/js/composers/basic_composer.js b/addons/mail/static/src/js/composers/basic_composer.js
deleted file mode 100644
index ffde77eac9a..00000000000
--- a/addons/mail/static/src/js/composers/basic_composer.js
+++ /dev/null
@@ -1,864 +0,0 @@
-odoo.define('mail.composer.Basic', function (require) {
-"use strict";
-
-var emojis = require('mail.emojis');
-var MentionManager = require('mail.composer.MentionManager');
-var DocumentViewer = require('mail.DocumentViewer');
-var utils = require('web.utils');
-
-var config = require('web.config');
-var core = require('web.core');
-var data = require('web.data');
-var dom = require('web.dom');
-var session = require('web.session');
-var Widget = require('web.Widget');
-
-var QWeb = core.qweb;
-var _t = core._t;
-
-var BasicComposer = Widget.extend({
- template: "mail.Composer",
- events: {
- 'change input.o_input_file': '_onAttachmentChange',
- 'click .o_attachment_delete': '_onAttachmentDelete',
- 'click .o_attachment_download': '_onAttachmentDownload',
- 'click .o_attachment_view': '_onAttachmentView',
- 'click .o_composer_button_add_attachment': '_onClickAddAttachment',
- 'click .o_composer_button_emoji': '_onEmojiButtonClick',
- 'focusout .o_composer_button_emoji': '_onEmojiButtonFocusout',
- 'mousedown .o_mail_emoji_container .o_mail_emoji': '_onEmojiImageClick',
- 'focus .o_mail_emoji_container .o_mail_emoji': '_onEmojiImageFocus',
- 'dragover .o_file_drop_zone_container': '_onFileDragover',
- 'drop .o_file_drop_zone_container': '_onFileDrop',
- 'input .o_input': '_onInput',
- 'keydown .o_composer_input textarea': '_onKeydown',
- 'keyup .o_composer_input': '_onKeyup',
- 'click .o_composer_button_send': '_sendMessage',
- },
- // RPCs done to fetch the mention suggestions are throttled with the
- // following value
- MENTION_THROTTLE: 200,
-
- init: function (parent, options) {
- this._super.apply(this, arguments);
- this.options = _.defaults(options || {}, {
- commandsEnabled: true,
- context: {},
- inputMinHeight: 28,
- mentionFetchLimit: 8,
- // set to true to only suggest prefetched partners
- mentionPartnersRestricted: false,
- sendText: _t("Send"),
- defaultBody: '',
- defaultMentionSelections: {},
- isMobile: config.device.isMobile
- });
- this.context = this.options.context;
-
- // Attachments
- this._attachmentDataSet = new data.DataSetSearch(this, 'ir.attachment', this.context);
- this.fileuploadID = _.uniqueId('o_chat_fileupload');
- this.set('attachment_ids', options.attachmentIds || []);
-
- // Mention
- this._mentionManager = new MentionManager(this);
- this._mentionManager.register({
- delimiter: '@',
- fetchCallback: this._mentionFetchPartners.bind(this),
- generateLinks: true,
- model: 'res.partner',
- redirectClassname: 'o_mail_redirect',
- selection: this.options.defaultMentionSelections['@'],
- suggestionTemplate: 'mail.MentionSuggestions.Partner',
- });
- this._mentionManager.register({
- delimiter: '#',
- fetchCallback: this._mentionFetchChannels.bind(this),
- generateLinks: true,
- model: 'mail.channel',
- redirectClassname: 'o_channel_redirect',
- selection: this.options.defaultMentionSelections['#'],
- suggestionTemplate: 'mail.MentionSuggestions.Channel',
- });
- this._mentionManager.register({
- delimiter: ':',
- fetchCallback: this._mentionGetCannedResponses.bind(this),
- selection: this.options.defaultMentionSelections[':'],
- suggestionTemplate: 'mail.MentionSuggestions.CannedResponse',
- });
- if (this.options.commandsEnabled) {
- this._mentionManager.register({
- beginningOnly: true,
- delimiter: '/',
- fetchCallback: this._mentionGetCommands.bind(this),
- selection: this.options.defaultMentionSelections['/'],
- suggestionTemplate: 'mail.MentionSuggestions.Command',
- });
- }
-
- this.isMini = options.isMini;
-
- this.avatarURL = session.uid > 0 ? session.url('/web/image', {
- model: 'res.users',
- field: 'image_128',
- id: session.uid,
- }) : '/web/static/src/img/user_menu_avatar.png';
- },
- start: function () {
- var self = this;
-
- this._$attachmentButton = this.$('.o_composer_button_add_attachment');
- this._$attachmentsList = this.$('.o_composer_attachments_list');
- this.$input = this.$('.o_composer_input textarea');
- this.$input.focus(function () {
- self.trigger('input_focused');
- });
- this.$input.val(this.options.defaultBody);
- dom.autoresize(this.$input, {
- parent: this,
- min_height: this.options.inputMinHeight
- });
-
- // Attachments
- this._renderAttachments();
- $(window).on(this.fileuploadID, this._onAttachmentLoaded.bind(this));
- this.on('change:attachment_ids', this, this._renderAttachments);
-
- this.call('mail_service', 'getMailBus')
- .on('update_typing_partners', this, this._onUpdateTypingPartners);
-
- // Mention
- var prependPromise = this._mentionManager.prependTo(this.$('.o_composer'));
-
- // Drag-Drop files
- // Allowing body to detect dragenter and dragleave for display
- var $body = $('body');
- this._dropZoneNS = _.uniqueId('o_dz_'); // For event namespace used when multiple chat window is open
- $body.on('dragleave.' + this._dropZoneNS, this._onBodyFileDragLeave.bind(this));
- $body.on("dragover." + this._dropZoneNS, this._onBodyFileDragover.bind(this));
- $body.on("drop." + this._dropZoneNS, this._onBodyFileDrop.bind(this));
- return Promise.all([this._super(), prependPromise]);
- },
- destroy: function () {
- $("body").off('dragleave.' + this._dropZoneNS);
- $("body").off('dragover.' + this._dropZoneNS);
- $("body").off('drop.' + this._dropZoneNS);
- $(window).off(this.fileuploadID);
- return this._super.apply(this, arguments);
- },
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Empty input, selected partners and attachments
- */
- clearComposer: function () {
- this.$input.val('');
- this._mentionManager.resetSelections();
- this.set('attachment_ids', []);
- },
- /**
- * Set the focus on the input
- */
- focus: function () {
- this.$input.focus();
- },
- /**
- * Get cursor position and selection
- *
- * @returns {Object} a current cursor position as { start: {integer}, end: {integer} }
- */
- getSelectionPositions: function () {
- var InputElement = this.$input.get(0);
- return InputElement ? dom.getSelectionRange(InputElement) : { start: 0, end: 0 };
- },
- /**
- * Get the state of the composer.
- *
- * This is useful in order to (re)store its state when switchng to another
- * thread with a composer, and coming back to the thread with this composer.
- *
- * @returns {Object}
- */
- getState: function () {
- return {
- attachments: this.get('attachment_ids'),
- text: this.$input.val(),
- };
- },
- /**
- * @returns {integer} it returns length of attachment and trim content of
- * input box to check for empty.
- */
- isEmpty: function () {
- return !this.$input.val().trim() &&
- !this.$('.o_attachments').children().length;
- },
- /**
- * Set the state of the composer.
- *
- * This is useful in order to (re)store its state when switching to another
- * thread with a composer, and coming back to the thread with this composer.
- *
- * @param {Object} state
- * @param {Array} state.attachments
- * @param {string} state.text
- */
- setState: function (state) {
- this.set('attachment_ids', state.attachments);
- this.$input.val(state.text);
- },
- /**
- * Set the thread that this composer refers to.
- *
- * @param {mail.model.Thread} thread
- */
- setThread: function (thread) {
- this.options.thread = thread;
- },
- /**
- * Set the list of command suggestions on the thread.
- *
- * When the user partially types a command, matching commands are displayed
- * to the user (@see _mentionGetCommands).
- *
- * @param {Object[]} commands
- */
- mentionSetCommands: function (commands) {
- this._mentionCommands = commands;
- },
- /**
- * Set the list of partner mentions suggestions that has been prefetched.
- *
- * When the user partially types a mention, matching prefetched partners are
- * displayed to the user. If none of them match, then it will fetch for more
- * partner suggestions (@see _mentionFetchPartners).
- *
- * @param {Promise<Object[]>} prefetchedPartners list of list of
- * prefetched partners.
- */
- mentionSetPrefetchedPartners: function (prefetchedPartners) {
- this._mentionPrefetchedPartners = prefetchedPartners;
- },
- /**
- * @returns {Object} containing listener selections, grouped by delimiter
- * as key of the object.
- */
- getMentionListenerSelections: function () {
- return this._mentionManager.getListenerSelections();
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * @private
- */
- _clearComposerOnSend: function () {
- this.clearComposer();
- },
- /**
- * @private
- */
- _doCheckAttachmentUpload: function () {
- if (_.find(this.get('attachment_ids'), function (file) { return file.upload; })) {
- this.do_warn(false, _t("Please wait while the file is uploading..."));
- return false;
- }
- return true;
- },
- /**
- * Hides the emojis container.
- *
- * @private
- */
- _hideEmojis: function () {
- this._$emojisContainer.remove();
- },
- /**
- * Making sure that dragging content is external files.
- * Ignoring other content draging like text.
- *
- * @private
- * @param {DataTransfer} dataTransfer
- * @returns {boolean}
- */
- _isDragSourceExternalFile: function (dataTransfer) {
- var DragDataType = dataTransfer.types;
- if (DragDataType.constructor === DOMStringList) {
- return DragDataType.contains('Files');
- }
- if (DragDataType.constructor === Array) {
- return DragDataType.indexOf('Files') !== -1;
- }
- return false;
- },
- /**
- * @private
- * @param {string} search
- * @returns {Promise<Object[]>}
- */
- _mentionGetCannedResponses: function (search) {
- var self = this;
- var def = new Promise(function (resolve, reject) {
- clearTimeout(self._cannedTimeout);
- self._cannedTimeout = setTimeout(function () {
- var cannedResponses = self.call('mail_service', 'getCannedResponses');
- var matches = fuzzy.filter(utils.unaccent(search), _.pluck(cannedResponses, 'source'));
- var indexes = _.pluck(matches.slice(0, self.options.mentionFetchLimit), 'index');
- resolve(_.map(indexes, function (index) {
- return cannedResponses[index];
- }));
- }, 500);
- });
- return def;
- },
- /**
- * @private
- * @param {string} search
- * @returns {Array}
- */
- _mentionGetCommands: function (search) {
- var searchRegexp = new RegExp(_.str.escapeRegExp(utils.unaccent(search)), 'i');
- return _.filter(this._mentionCommands, function (command) {
- return searchRegexp.test(command.name);
- }).slice(0, this.options.mentionFetchLimit);
- },
- /**
- * @private
- * @param {string} search
- */
- _mentionFetchChannels: function (search) {
- return this._mentionFetchThrottled('mail.channel', 'get_mention_suggestions', {
- limit: this.options.mentionFetchLimit,
- search: search,
- }).then(function (suggestions) {
- return _.partition(suggestions, function (suggestion) {
- return _.contains(['public', 'groups'], suggestion.public);
- });
- });
- },
- /**
- * @private
- * @param {string} search
- */
- _mentionFetchPartners: function (search) {
- var self = this;
- return Promise.resolve(this._mentionPrefetchedPartners).then(function (prefetchedPartners) {
- // filter prefetched partners with the given search string
- var suggestions = [];
- var limit = self.options.mentionFetchLimit;
- var searchRegexp = new RegExp(_.str.escapeRegExp(utils.unaccent(search)), 'i');
- _.each(prefetchedPartners, function (partners) {
- if (limit > 0) {
- var filteredPartners = _.filter(partners, function (partner) {
- return partner.email && searchRegexp.test(partner.email) ||
- partner.name && searchRegexp.test(utils.unaccent(partner.name));
- });
- if (filteredPartners.length) {
- suggestions.push(filteredPartners.slice(0, limit));
- limit -= filteredPartners.length;
- }
- }
- });
- if (!suggestions.length && !self.options.mentionPartnersRestricted) {
- // no result found among prefetched partners, fetch other suggestions
- suggestions = self._mentionFetchThrottled(
- 'res.partner',
- 'get_mention_suggestions',
- { limit: limit, search: search }
- );
- }
- return Promise.resolve(suggestions).then(function (suggestions) {
- //add im_status on suggestions
- _.each(suggestions, function (suggestionsSet) {
- _.each(suggestionsSet, function (suggestion) {
- suggestion.im_status = self.call('mail_service', 'getImStatus', { partnerID: suggestion.id });
- });
- });
- return suggestions;
- });
- });
- },
- /**
- * @private
- * @param {string} model
- * @param {string} method
- * @param {Object} kwargs
- * @return {Promise}
- */
- _mentionFetchThrottled: function (model, method, kwargs) {
- var self = this;
- // Delays the execution of the RPC to prevent unnecessary RPCs when the user is still typing
- return new Promise(function (resolve, reject) {
- clearTimeout(self.mentionFetchTimer);
- self.mentionFetchTimer = setTimeout(function () {
- return self._rpc({model: model, method: method, kwargs: kwargs})
- .then(function (results) {
- resolve(results);
- });
- }, self.MENTION_THROTTLE);
- });
- },
- /**
- * @private
- * @returns {Promise}
- */
- _preprocessMessage: function () {
- // Return a promise as this function is extended with asynchronous
- // behavior for the chatter composer
-
- //Removing unwanted extra spaces from message
- var value = _.escape(this.$input.val()).trim();
- value = value.replace(/(\r|\n){2,}/g, '<br/><br/>');
- value = value.replace(/(\r|\n)/g, '<br/>');
-
- // prevent html space collapsing
- value = value.replace(/ /g, '&nbsp;').replace(/([^>])&nbsp;([^<])/g, '$1 $2');
- var commands = this.options.commandsEnabled ?
- this._mentionManager.getListenerSelection('/') :
- [];
- return Promise.resolve({
- content: this._mentionManager.generateLinks(value),
- attachment_ids: _.pluck(this.get('attachment_ids'), 'id'),
- partner_ids: _.uniq(_.pluck(this._mentionManager.getListenerSelection('@'), 'id')),
- canned_response_ids: _.uniq(_.pluck(this._mentionManager.getListenerSelections()[':'], 'id')),
- channel_ids: _.uniq(_.pluck(this._mentionManager.getListenerSelection('#'), 'id')),
- command: commands.length > 0 ? commands[0].name : undefined,
- });
- },
- /**
- * Allowing to upload attachment with file selector as well drag drop feature.
- *
- * @private
- * @param {Array<File>} params.files
- * @param {boolean} params.submitForm [optional]
- */
- _processAttachmentChange: function (params) {
- var self = this;
- var attachments = this.get('attachment_ids');
- var files = params.files;
- var submitForm = params.submitForm;
- var $form = this.$('form.o_form_binary_form');
-
- /**
- * makes a new formData as formData.delete() is not supported by IE or Safari Mobile.
- *
- * @return {FormData}
- */
- function makeFormDataWithoutUfile() {
- var newFormData = new window.FormData();
- $form.find('input').each(function (index, input) {
- if (input.name !== 'ufile') {
- newFormData.append(input.name, input.value);
- }
- });
- return newFormData;
- }
-
- _.each(files, function (file) {
- var attachment = _.findWhere(attachments, {
- name: file.name,
- size: file.size
- });
- // if the files already exits, delete the file before upload
- if (attachment) {
- self._attachmentDataSet.unlink([attachment.id]);
- attachments = _.without(attachments, attachment);
- }
- });
- if (submitForm) {
- $form.submit();
- this._$attachmentButton.prop('disabled', true);
- } else {
- _.each(files, function (file) {
- var formData = makeFormDataWithoutUfile();
- formData.append("ufile", file, file.name);
- $.ajax({
- url: $form.attr("action"),
- type: "POST",
- enctype: 'multipart/form-data',
- processData: false,
- contentType: false,
- data: formData,
- success: function (result) {
- var $el = $(result);
- $.globalEval($el.contents().text());
- }
- });
- });
- }
- var uploadAttachments = _.map(files, function (file){
- return {
- id: 0,
- name: file.name,
- filename: file.name,
- url: '',
- upload: true,
- mimetype: '',
- };
- });
- attachments = attachments.concat(uploadAttachments);
- this.set('attachment_ids', attachments);
- },
- /**
- * @private
- */
- _renderAttachments: function () {
- this._$attachmentsList.html(QWeb.render('mail.composer.Attachments', {
- attachments: this.get('attachment_ids'),
- }));
- },
- /**
- * @private
- */
- _sendMessage: function () {
- if (this.isEmpty() || !this._doCheckAttachmentUpload()) {
- return;
- }
-
- clearTimeout(this._cannedTimeout);
- var self = this;
- this._preprocessMessage().then(function (message) {
- self.trigger('post_message', message);
- self._clearComposerOnSend();
- self.$input.focus();
- });
- },
- /**
- * Send the message on ENTER, but go to new line on SHIFT+ENTER
- *
- * @private
- */
- _shouldSend: function (ev) {
- return !ev.shiftKey;
- },
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * @private
- * @param {jQuery.Event} ev
- */
- _onAttachmentChange: function (ev) {
- this._processAttachmentChange({
- files: ev.currentTarget.files,
- submitForm: true
- });
- ev.target.value = "";
- },
- /**
- * @private
- * @param {MouseEvent} ev
- */
- _onAttachmentDelete: function (ev){
- ev.stopPropagation();
- var self = this;
- var attachmentID = $(ev.currentTarget).data('id');
- if (attachmentID) {
- var attachments = [];
- _.each(this.get('attachment_ids'), function (attachment){
- if (attachmentID !== attachment.id) {
- attachments.push(attachment);
- } else {
- self._attachmentDataSet.unlink([attachmentID]);
- }
- });
- this.set('attachment_ids', attachments);
- this.$('input.o_input_file').val('');
- }
- },
- /**
- * @private
- * @param {MouseEvent} ev
- */
- _onAttachmentDownload: function (ev) {
- ev.stopPropagation();
- },
- /**
- * @private
- */
- _onAttachmentLoaded: function () {
- var attachments = this.get('attachment_ids');
- var files = Array.prototype.slice.call(arguments, 1);
-
- _.each(files, function (file) {
- if (file.error || !file.id){
- this.do_warn(file.error);
- attachments = _.filter(attachments, function (attachment) {
- return !attachment.upload;
- });
- } else {
- var attachment = _.findWhere(attachments, { filename: file.filename, upload: true });
- if (attachment) {
- attachments = _.without(attachments, attachment);
- attachments.push({
- id: file.id,
- name: file.name || file.filename,
- filename: file.filename,
- mimetype: file.mimetype,
- size: file.size,
- url: session.url('/web/content', { id: file.id, download: true }),
- });
- }
- }
- }.bind(this));
- this.set('attachment_ids', attachments);
- this._$attachmentButton.prop('disabled', false);
- },
- /**
- * @private
- * @param {MouseEvent} ev
- */
- _onAttachmentView: function (ev) {
- var activeAttachmentID = $(ev.currentTarget).data('id');
- var attachments = this.get('attachment_ids');
- if (activeAttachmentID) {
- var attachmentViewer = new DocumentViewer(this, attachments, activeAttachmentID);
- attachmentViewer.appendTo($('body'));
- }
- },
- /**
- * @private
- * @param {MouseEvent} ev
- */
- _onBodyFileDragLeave: function (ev) {
- // On every dragenter chain created with parent child element
- // That's why dragleave is fired every time when a child elemnt is hovered
- // so here we hide dropzone based on mouse position
- if (ev.originalEvent.clientX <= 0
- || ev.originalEvent.clientY <= 0
- || ev.originalEvent.clientX >= window.innerWidth
- || ev.originalEvent.clientY >= window.innerHeight
- ) {
- this.$(".o_file_drop_zone_container").addClass("d-none");
- }
- },
- /**
- * When user start dragging on element drop area will be visible to drop selected files.
- *
- * @private
- * @param {MouseEvent} ev
- */
- _onBodyFileDragover: function (ev) {
- ev.preventDefault();
- if (this._isDragSourceExternalFile(ev.originalEvent.dataTransfer)) {
- this.$(".o_file_drop_zone_container").removeClass("d-none");
- }
- },
- /**
- * @private
- * @param {MouseEvent} ev
- */
- _onBodyFileDrop: function (ev) {
- ev.preventDefault();
- this.$(".o_file_drop_zone_container").addClass("d-none");
- },
- /**
- * @private
- */
- _onClickAddAttachment: function () {
- this.$('input.o_input_file').click();
- this.$input.focus();
- },
- /**
- * Called when the emoji button is clicked -> opens/hides the emoji panel.
- * Also, this method is in charge of the rendering of this panel the first
- * time it is opened.
- *
- * @private
- */
- _onEmojiButtonClick: function () {
- if (!this._$emojisContainer) { // lazy rendering
- this._$emojisContainer = $(QWeb.render('mail.Composer.emojis', {
- emojis: emojis,
- }));
- }
- if (this._$emojisContainer.parent().length) {
- this._hideEmojis();
- } else {
- this._$emojisContainer.appendTo(this.$('.o_composer'));
- }
- },
- /**
- * Called when the emoji button is blurred -> closes the emoji panel. The
- * closing is scheduled to be done at the end of the current execution
- * stack to allow stoping this closing if the button was focusout to select
- * an emoji (for example).
- *
- * @private
- */
- _onEmojiButtonFocusout: function () {
- if (this._$emojisContainer) {
- this._hideEmojisTimeout = setTimeout(this._hideEmojis.bind(this), 0);
- }
- },
- /**
- * Called when an emoji is clicked from the emoji panel.
- * Emoji is inserted in the composer based on the position of the cursor,
- * and it automatically focuses the composer and set the cursor position
- * just after the newly inserted emoji.
- *
- * @private
- * @param {Event} ev
- */
- _onEmojiImageClick: function (ev) {
- ev.preventDefault();
- var cursorPosition = this.getSelectionPositions();
- var inputVal = this.$input.val();
- var leftSubstring = inputVal.substring(0, cursorPosition.start);
- var rightSubstring = inputVal.substring(cursorPosition.end);
- var newInputVal = [leftSubstring , $(ev.currentTarget).data('emoji'), rightSubstring].join(" ");
- var newCursorPosition = newInputVal.length - rightSubstring.length;
- this.$input.val(newInputVal);
- this.$input.focus();
- this.$input[0].setSelectionRange(newCursorPosition, newCursorPosition);
- this._hideEmojis();
- },
- /**
- * Called when an emoji is focused -> @see _onEmojiButtonFocusout
- *
- * @private
- */
- _onEmojiImageFocus: function () {
- clearTimeout(this._hideEmojisTimeout);
- },
- /**
- * Setting drop Effect to copy so when mouse pointer on dropzone
- * cursor icon changed to copy ('+')
- *
- * @private
- * @param {MouseEvent} ev
- */
- _onFileDragover: function (ev) {
- ev.originalEvent.dataTransfer.dropEffect = "copy";
- },
- /**
- * Called when user drop selected files on drop area
- *
- * @private
- * @param {MouseEvent} ev
- */
- _onFileDrop: function (ev) {
- ev.preventDefault();
- // FIX: In case multiple chat windows are opened, and file droped in one of them
- // at that time, other chat windows are still displaing drop areas so here hide them all with $ selector
- $(".o_file_drop_zone_container").addClass("d-none");
- if (this._isDragSourceExternalFile(ev.originalEvent.dataTransfer)) {
- var files = ev.originalEvent.dataTransfer.files;
- this._processAttachmentChange({ files: files });
- }
- },
- /**
- * Called when the input in the composer changes
- *
- * @private
- */
- _onInput: function () {
- if (this.options.thread && this.options.thread.hasTypingNotification()) {
- var isTyping = this.$input.val().length > 0;
- this.options.thread.setMyselfTyping({ typing: isTyping });
- }
- },
- /**
- * _onKeydown event is triggered when is key is pressed
- * - on UP and DOWN arrow is pressed then event prevents it's default
- * behaviour if mention manager is open else it break it.
- * - on ENTER key is pressed and mention manager is open then event
- * prevents it's default behaviour else check if ControlKey is
- * pressed or not if yes then it send a message
- *
- * @private
- * @param {KeyboardEvent} ev
- */
- _onKeydown: function (ev) {
- switch (ev.which) {
- // UP, DOWN, TAB: prevent moving cursor if navigation in mention propositions
- case $.ui.keyCode.UP:
- case $.ui.keyCode.DOWN:
- case $.ui.keyCode.TAB:
- if (this._mentionManager.isOpen()) {
- ev.preventDefault();
- }
- break;
- // ENTER: submit the message only if the dropdown mention proposition is not displayed
- case $.ui.keyCode.ENTER:
- if (this._mentionManager.isOpen()) {
- ev.preventDefault();
- } else {
- var sendMessage = ev.ctrlKey || this._shouldSend(ev);
- if (sendMessage) {
- ev.preventDefault();
- this._sendMessage();
- }
- }
- break;
- }
- },
- /**
- * _onKeyup event is triggered when key is released.
- * on ESCAP key it close the mention suggestion dropdown menu.
- * on ENTER key it selects that mention and add to a content editable div.
- * on UP or DOWN key it highlights previous or next mention respectively.
- *
- * @private
- * @param {KeyboardEvent} ev
- */
- _onKeyup: function (ev) {
- switch (ev.which) {
- // ESCAPED KEYS: do nothing
- case $.ui.keyCode.END:
- case $.ui.keyCode.PAGE_UP:
- case $.ui.keyCode.PAGE_DOWN:
- break;
- // ESCAPE: close mention propositions
- case $.ui.keyCode.ESCAPE:
- if (this._mentionManager.isOpen()) {
- ev.stopPropagation();
- this._mentionManager.resetSuggestions();
- } else {
- this.trigger_up('escape_pressed');
- }
- break;
- // ENTER, UP, DOWN, TAB: check if navigation in mention propositions
- case $.ui.keyCode.ENTER:
- case $.ui.keyCode.UP:
- case $.ui.keyCode.DOWN:
- case $.ui.keyCode.TAB:
- if (this._mentionManager.isOpen()) {
- this._mentionManager.propositionNavigation(ev.which);
- }
- break;
- // Otherwise, check if a mention is typed
- default:
- this._mentionManager.detectDelimiter();
- }
- },
- /**
- * @private
- * @param {integer|string} threadID
- */
- _onUpdateTypingPartners: function (threadID) {
- if (!this.options.showTyping) {
- return;
- }
- if (!this.options.thread) {
- return;
- }
- if (this.options.thread.getID() !== threadID) {
- return;
- }
- this.$('.o_composer_thread_typing').html(QWeb.render('mail.Composer.ThreadTyping', {
- thread: this.options.thread,
- }));
- },
-});
-
-return BasicComposer;
-
-});