From e870ccf9ab541a4a2660a73cfcce46c0ca82a860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Debauche=20St=C3=A9phane?= Date: Fri, 8 Nov 2019 09:51:21 +0000 Subject: [PATCH 1/4] [MOV] mail, social: move emojis mixin and field from social to mail PURPOSE Allow to use the text field with integrated emojis support in other apps notably mass mailing SPECIFICATIONS Move emojis mixin and FieldTextEmojis field definition from social to mail application. Rename emojis namespace accordingly. LINKS Task #2117238 PR #40045 Enterprise PR odoo/enterprise#6630 --- addons/mail/__manifest__.py | 1 + addons/mail/static/src/js/emojis_mixin.js | 105 ++++++++++++++ .../mail/static/src/js/text_emojis_field.js | 129 ++++++++++++++++++ addons/mail/static/src/scss/emojis.scss | 25 ++++ addons/mail/static/src/xml/text_emojis.xml | 18 +++ addons/mail/views/mail_templates.xml | 7 +- 6 files changed, 283 insertions(+), 2 deletions(-) create mode 100644 addons/mail/static/src/js/emojis_mixin.js create mode 100644 addons/mail/static/src/js/text_emojis_field.js create mode 100644 addons/mail/static/src/scss/emojis.scss create mode 100644 addons/mail/static/src/xml/text_emojis.xml 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 @@ - + - +