diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py index 5dc0f4425f1..5b14bb7a55a 100644 --- a/addons/mail/__manifest__.py +++ b/addons/mail/__manifest__.py @@ -62,5 +62,6 @@ 'static/src/xml/announcement.xml', 'static/src/xml/user_menu.xml', 'static/src/xml/web_kanban_activity.xml', + 'static/src/xml/text_emojis.xml', ], } diff --git a/addons/mail/static/src/js/emojis_mixin.js b/addons/mail/static/src/js/emojis_mixin.js new file mode 100644 index 00000000000..ed5aaa52bac --- /dev/null +++ b/addons/mail/static/src/js/emojis_mixin.js @@ -0,0 +1,105 @@ +odoo.define('mail.emoji_mixin', function (require) { +"use strict"; + +var dom = require('web.dom'); +var emojis = require('mail.emojis'); + +/** + * This mixin gathers a few methods that are used to handle emojis. + * + * It's used to: + * + * - handle the click on an emoji from a dropdown panel and add it to the related textarea + * - replace the emoji code with the actual character, ex: ' :) ' -> ' 😊 ' + * - format text and wrap the emojis around to make them look nicer + * + * Methods are based on the collections of emojis available in mail.emojis + * + */ +return { + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * This method should be bound to a click event on an emoji. + * (used in textarea's emojis dropdown list) + * + * It assumes that a '_getTargetTextArea' method is defined that will return the related + * $textarea element. + * + * Some code from 'basic_composer.js' was copy/pasted to handle the cursor position in the + * textarea and handle text replacement if text was selected by the user. + * + * @param {MouseEvent} ev + */ + _onEmojiClick: function (ev) { + ev.preventDefault(); + + var $target = $(ev.currentTarget); + var $textarea = this._getTargetTextArea($target); + + // copy/pasted from 'basic_composer.js' + var inputElement = $textarea.get(0); + var cursorPosition = inputElement ? dom.getSelectionRange(inputElement) : {start: 0, end: 0}; + var inputVal = $textarea.val(); + var leftSubstring = inputVal.substring(0, cursorPosition.start); + var rightSubstring = inputVal.substring(cursorPosition.end); + var newInputVal = [leftSubstring , $target.text().trim(), rightSubstring].join(""); + var newCursorPosition = newInputVal.length - rightSubstring.length; + $textarea.val(newInputVal); + $textarea.focus(); + $textarea[0].setSelectionRange(newCursorPosition, newCursorPosition); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * This method is used to wrap emojis in a text message with + * As this returns html to be used in a 't-raw' argument, it first makes sure that the + * passed text message is html escaped for safety reasons. + * + * @param {String} message a text message to format + */ + _formatText: function (message) { + message = this._htmlEscape(message); + message = this._wrapEmojis(message); + message = message.replace(/(?:\r\n|\r|\n)/g, '
'); + + return message; + }, + + /** + * Adapted from qweb2.js#html_escape to avoid formatting '&' + * + * @param {String} s + * @private + */ + _htmlEscape: function (s) { + if (s == null) { + return ''; + } + return String(s).replace(//g, '>'); + }, + + /** + * Will use the mail.emojis library to wrap emojis unicode around a span with a special font + * that will make them look nicer (colored, ...). + * + * @param {String} message + */ + _wrapEmojis: function (message) { + emojis.forEach(function (emoji) { + message = message.replace( + new RegExp(emoji.unicode.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), + '' + emoji.unicode + '' + ); + }); + + return message; + } +}; + +}); diff --git a/addons/mail/static/src/js/text_emojis_field.js b/addons/mail/static/src/js/text_emojis_field.js new file mode 100644 index 00000000000..07466bb7705 --- /dev/null +++ b/addons/mail/static/src/js/text_emojis_field.js @@ -0,0 +1,129 @@ +odoo.define('mail.text_emojis_field', function (require) { +"use strict"; + +var basicFields = require('web.basic_fields'); +var core = require('web.core'); +var emojis = require('mail.emojis'); +var registry = require('web.field_registry'); +var MailEmojisMixin = require('mail.emoji_mixin'); +var _onEmojiClickMixin = MailEmojisMixin._onEmojiClick; +var QWeb = core.qweb; + +var FieldTextEmojis = basicFields.FieldText.extend(MailEmojisMixin, { + /** + * @override + * @private + */ + init: function () { + this._super.apply(this, arguments); + this._updatePreview =_.throttle(this._updatePreview, 1000, {leading: false}); + this.emojis = emojis; + }, + + /** + * This will add an emoji button that shows the emojis selection dropdown. + * + * We use 'on_attach_callback' because we need the element to be attached to the form first. + * That's because the $emojisIcon element needs to be rendered outside of this $el + * (which is an textarea, that can't 'contain' any other elements). + * + * @override + */ + on_attach_callback: function () { + var self = this; + + if (!this.$emojisIcon) { + this.$emojisIcon = $(QWeb.render( + 'mail.EmojisDropdown', { + widget: this, + emojisDropdownId: 'emojis_dropdown' + } + )); + this.$emojisIcon.find('.o_mail_emoji').on('click', function (ev) { + self._onEmojiClick(ev); + self._isDirty = true; + }); + this.$el.after(this.$emojisIcon); + } + + if (this.mode === 'edit') { + this.$emojisIcon.show(); + } else { + this.$emojisIcon.hide(); + } + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @override + * @private + */ + _render: function () { + this._super.apply(this, arguments); + + if (this.mode !== 'edit') { + this.$el.html(this._formatText(this.$el.text())); + } + }, + + /** + * Overridden because we need to add the Emoji to the input AND trigger + * the 'change' event to refresh the various post previews. + * + * @override + * @private + */ + _onEmojiClick: function () { + _onEmojiClickMixin.apply(this, arguments); + this.$input.trigger('change'); + }, + + /** + * + * By default, the 'change' event is only triggered when the textarea is blurred. + * + * We override this method because we want to update the various post previews while + * the user is typing his message (and not only on blur). + * + * @override + * @private + */ + _onKeydown: function () { + this._super.apply(this, arguments); + this._updatePreview(); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Used by MailEmojisMixin, check its document for more info. + * + * @param {$.Element} $emoji + * @private + */ + _getTargetTextArea($emoji) { + return this.$el; + }, + + /** + * Triggers the 'change' event to refresh the various post previews. + * This method is throttled to run at most once every second. + * (to avoid spamming the server while the user is typing his message) + * + * @private + */ + _updatePreview: function () { + this.$input.trigger('change'); + } +}; + +registry.add('text_emojis', FieldTextEmojis); + +return FieldTextEmojis; + +}); diff --git a/addons/mail/static/src/scss/emojis.scss b/addons/mail/static/src/scss/emojis.scss new file mode 100644 index 00000000000..62ddb073e4c --- /dev/null +++ b/addons/mail/static/src/scss/emojis.scss @@ -0,0 +1,25 @@ +.o_mail_add_emoji { + float: right; + margin-bottom: 1rem; + .dropdown-menu { + .o_mail_emoji { + cursor: pointer; + padding: 2px; + + @include hover-focus() { + background-color: grey('100'); + } + } + } +} + +.o_mail_emojis_dropdown { + height: 35px; + width: 40px; + float: right; + margin-top: -35px; + + .dropdown-toggle:after { + display: none; + } +} diff --git a/addons/mail/static/src/xml/text_emojis.xml b/addons/mail/static/src/xml/text_emojis.xml new file mode 100644 index 00000000000..89f26daf18c --- /dev/null +++ b/addons/mail/static/src/xml/text_emojis.xml @@ -0,0 +1,18 @@ + + +
+ + +
+
diff --git a/addons/mail/views/mail_templates.xml b/addons/mail/views/mail_templates.xml index 90af177ee99..bc1f9442f6e 100644 --- a/addons/mail/views/mail_templates.xml +++ b/addons/mail/views/mail_templates.xml @@ -81,9 +81,12 @@ - + - +