[MERGE] mail, social: allow support of emojis in char fields in mail applications
PURPOSE
Allow to use the text field with integrated emojis support in other apps
notably mass mailing
SPECIFICATIONS
This purpose is achieved in several steps, each done with different
commits to ease understanding
* move emojis mixin and FieldTextEmojis field definition from social to mail
application. Rename emojis namespace accordingly;
* allow emojis support in chat fields through new field widget;
* use this new widget in mass mailing subject field;
* perform some code cleaning;
See sub commits for more details.
LINKS
Task #2117238
PR #40045
Enterprise PR odoo/enterprise#6630
Related: odoo/enterprise#6630
Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -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',
|
||||
],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
odoo.define('mail.emoji_mixin', function (require) {
|
||||
"use strict";
|
||||
|
||||
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/input
|
||||
* - format text and wrap the emojis around <span class="o_mail_emoji"> 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 text element's emojis dropdown list)
|
||||
*
|
||||
* It assumes that a ``_getTargetTextElement`` method is defined that will return the related
|
||||
* textarea/input element in which the emoji will be inserted.
|
||||
*
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onEmojiClick: function (ev) {
|
||||
var unicode = ev.currentTarget.textContent.trim();
|
||||
var textInput = this._getTargetTextElement($(ev.currentTarget))[0];
|
||||
var selectionStart = textInput.selectionStart;
|
||||
|
||||
textInput.value = textInput.value.slice(0, selectionStart) + unicode + textInput.value.slice(selectionStart);
|
||||
textInput.focus();
|
||||
textInput.setSelectionRange(selectionStart + unicode.length, selectionStart + unicode.length);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* This method is used to wrap emojis in a text message with <span class="o_mail_emoji">
|
||||
* 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, '<br>');
|
||||
|
||||
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, '<').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'),
|
||||
'<span class="o_mail_emoji">' + emoji.unicode + '</span>'
|
||||
);
|
||||
});
|
||||
|
||||
return message;
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,136 @@
|
||||
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;
|
||||
|
||||
/*
|
||||
* Common code for FieldTextEmojis and FieldCharEmojis
|
||||
*/
|
||||
var FieldEmojiCommon = {
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
this._triggerOnchange =_.throttle(this._triggerOnchange, 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 text element, 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}));
|
||||
this.$emojisIcon.find('.o_mail_emoji').on('click', this._onEmojiClick.bind(this));
|
||||
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 value.
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onEmojiClick: function () {
|
||||
_onEmojiClickMixin.apply(this, arguments);
|
||||
self._isDirty = true;
|
||||
this.$input.trigger('change');
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* By default, the 'change' event is only triggered when the text element is blurred.
|
||||
*
|
||||
* We override this method because we want to update the value while
|
||||
* the user is typing his message (and not only on blur).
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onKeydown: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (this.nodeOptions.onchange_on_keydown) {
|
||||
this._triggerOnchange();
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Used by MailEmojisMixin, check its document for more info.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_getTargetTextElement() {
|
||||
return this.$el;
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggers the 'change' event to refresh the value.
|
||||
* This method is throttled to run at most once every second.
|
||||
* (to avoid spamming the server while the user is typing his message)
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_triggerOnchange: function () {
|
||||
this.$input.trigger('change');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extension of the FieldText that will add emojis support
|
||||
*/
|
||||
var FieldTextEmojis = basicFields.FieldText.extend(MailEmojisMixin, FieldEmojiCommon);
|
||||
|
||||
/**
|
||||
* Extension of the FieldChar that will add emojis support
|
||||
*/
|
||||
var FieldCharEmojis = basicFields.FieldChar.extend(MailEmojisMixin, FieldEmojiCommon);
|
||||
|
||||
registry.add('text_emojis', FieldTextEmojis);
|
||||
registry.add('char_emojis', FieldCharEmojis);
|
||||
|
||||
return {FieldTextEmojis: FieldTextEmojis, FieldCharEmojis: FieldCharEmojis};
|
||||
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
.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: 25px;
|
||||
width: 40px;
|
||||
float: right;
|
||||
margin-top: -35px;
|
||||
|
||||
* {
|
||||
outline: none!important;
|
||||
box-shadow: none!important;
|
||||
}
|
||||
|
||||
.dropdown-toggle:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
<div t-name="mail.EmojisDropdown" class="o_mail_emojis_dropdown o_mail_add_emoji dropdown">
|
||||
<button class="btn btn-block dropdown-toggle px-3 py-1"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false"
|
||||
title="Add an emoji">
|
||||
<i class="fa fa-smile-o"/>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right border-0 p-2" style="width: 406px">
|
||||
<t t-call="mail.Composer.emojis">
|
||||
<t t-set="emojis" t-value="widget.emojis"></t>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -81,9 +81,12 @@
|
||||
<script type="text/javascript" src="/mail/static/src/js/views/activity/activity_record.js"></script>
|
||||
<!-- Attachment box -->
|
||||
<script type="text/javascript" src="/mail/static/src/js/attachment_box.js"></script>
|
||||
<!-- Others -->
|
||||
<!-- Emojis -->
|
||||
<script type="text/javascript" src="/mail/static/src/js/emojis.js"></script>
|
||||
|
||||
<script type="text/javascript" src="/mail/static/src/js/emojis_mixin.js"/>
|
||||
<script type="text/javascript" src="/mail/static/src/js/text_emojis_fields.js"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/mail/static/src/scss/emojis.scss"/>
|
||||
<!-- Others -->
|
||||
<link rel="stylesheet" type="text/scss" href="/mail/static/src/scss/announcement.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/mail/static/src/scss/discuss.scss"/>
|
||||
<link rel="stylesheet" type="text/scss" href="/mail/static/src/scss/abstract_thread_window.scss"/>
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
<field name="active" invisible="1"/>
|
||||
<field name="mailing_type" widget="radio" options="{'horizontal': true}" invisible="1"
|
||||
attrs="{'readonly': [('state', 'in', ('sending', 'done'))]}"/>
|
||||
<field name="subject" string="Subject" attrs="{'readonly': [('state', 'in', ('sending', 'done'))]}"/>
|
||||
<field name="subject" string="Subject" attrs="{'readonly': [('state', 'in', ('sending', 'done'))]}" widget="char_emojis"/>
|
||||
<label for="mailing_model_id" string="Recipients"/>
|
||||
<div name="mailing_model_id_container">
|
||||
<div class="row">
|
||||
|
||||
Reference in New Issue
Block a user