[IMP] mail, mass_mailing, im_livechat: port emoji fields to OWL
This ports the `text_emojis` and `char_emojis` fields to OWL. X-original-commit: 65b3e551aee0ea451a7f48c6778ce78931bff3e8 Part-of: odoo/odoo#101350
This commit is contained in:
@@ -16,7 +16,7 @@ const commonSteps = [tour.stepUtils.showAppsMenuItem(), {
|
||||
}, {
|
||||
trigger: 'div[name="script_step_ids"] .o_field_x2many_list_row_add a'
|
||||
}, {
|
||||
trigger: 'textarea[name="message"]',
|
||||
trigger: 'textarea#message',
|
||||
run: 'text Step 1'
|
||||
}, {
|
||||
trigger: 'button:contains("Save & New")'
|
||||
@@ -25,7 +25,7 @@ const commonSteps = [tour.stepUtils.showAppsMenuItem(), {
|
||||
in_modal: false,
|
||||
run: () => {}
|
||||
}, {
|
||||
trigger: 'textarea[name="message"]',
|
||||
trigger: 'textarea#message',
|
||||
run: 'text Step 2'
|
||||
}, {
|
||||
trigger: 'button:contains("Save & New")'
|
||||
@@ -34,7 +34,7 @@ const commonSteps = [tour.stepUtils.showAppsMenuItem(), {
|
||||
in_modal: false,
|
||||
run: () => {}
|
||||
}, {
|
||||
trigger: 'textarea[name="message"]',
|
||||
trigger: 'textarea#message',
|
||||
run: 'text Step 3'
|
||||
}];
|
||||
|
||||
@@ -68,7 +68,7 @@ tour.register('im_livechat_chatbot_steps_sequence_with_move_tour', {
|
||||
in_modal: false,
|
||||
run: () => {}
|
||||
}, {
|
||||
trigger: 'textarea[name="message"]',
|
||||
trigger: 'textarea#message',
|
||||
run: 'text Step 4'
|
||||
}, {
|
||||
trigger: 'button:contains("Save & New")'
|
||||
@@ -77,7 +77,7 @@ tour.register('im_livechat_chatbot_steps_sequence_with_move_tour', {
|
||||
in_modal: false,
|
||||
run: () => {}
|
||||
}, {
|
||||
trigger: 'textarea[name="message"]',
|
||||
trigger: 'textarea#message',
|
||||
run: 'text Step 5'
|
||||
}, {
|
||||
trigger: 'button:contains("Save & Close")'
|
||||
@@ -108,7 +108,7 @@ tour.register('im_livechat_chatbot_steps_sequence_with_move_tour', {
|
||||
}, {
|
||||
trigger: 'div[name="script_step_ids"] .o_field_x2many_list_row_add a'
|
||||
}, {
|
||||
trigger: 'textarea[name="message"]',
|
||||
trigger: 'textarea#message',
|
||||
run: 'text Step 6'
|
||||
}, {
|
||||
trigger: 'button:contains("Save & Close")'
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import emojis from '@mail/js/emojis';
|
||||
|
||||
const { Component, useRef, onMounted } = owl;
|
||||
|
||||
export class EmojisDropdown extends Component {
|
||||
setup() {
|
||||
this.toggleRef = useRef('toggleRef');
|
||||
this.emojis = emojis;
|
||||
super.setup();
|
||||
onMounted(() => {
|
||||
new Dropdown(this.toggleRef.el, {
|
||||
popperConfig: { placement: 'bottom-end', strategy: 'fixed' },
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
EmojisDropdown.template = 'mail.EmojisDropdown';
|
||||
@@ -28,10 +28,10 @@ export default {
|
||||
*
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
_onEmojiClick: function (ev) {
|
||||
var unicode = ev.currentTarget.textContent.trim();
|
||||
var textInput = this._getTargetTextElement($(ev.currentTarget))[0];
|
||||
var selectionStart = textInput.selectionStart;
|
||||
onEmojiClick(ev) {
|
||||
const unicode = ev.currentTarget.textContent.trim();
|
||||
const textInput = this._getTargetTextElement($(ev.currentTarget));
|
||||
const selectionStart = textInput.selectionStart;
|
||||
|
||||
textInput.value = textInput.value.slice(0, selectionStart) + unicode + textInput.value.slice(selectionStart);
|
||||
textInput.focus();
|
||||
@@ -49,7 +49,7 @@ export default {
|
||||
*
|
||||
* @param {String} message a text message to format
|
||||
*/
|
||||
_formatText: function (message) {
|
||||
_formatText(message) {
|
||||
message = escape(message);
|
||||
message = this._wrapEmojis(message);
|
||||
message = message.replace(/(?:\r\n|\r|\n)/g, '<br>');
|
||||
@@ -63,7 +63,7 @@ export default {
|
||||
*
|
||||
* @param {String} message
|
||||
*/
|
||||
_wrapEmojis: function (message) {
|
||||
_wrapEmojis(message) {
|
||||
emojis.forEach(function (emoji) {
|
||||
message = message.replace(
|
||||
new RegExp(emoji.unicode.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'),
|
||||
|
||||
@@ -17,20 +17,11 @@ $o-mail-emoji-height: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.o_form_view {
|
||||
// Emojis widgets should hide the emoji dropdown button when the field is invisible.
|
||||
// This is necessary because the button is added *after* the main element (and not inside)
|
||||
// (see '_attachEmojisDropdown' for more details)
|
||||
.o_invisible_modifier + .o_mail_add_emoji, .o_invisible_modifier + textarea + .o_mail_add_emoji{
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.o_mail_emojis_dropdown {
|
||||
height: $o-mail-emoji-height;
|
||||
width: 40px;
|
||||
float: right;
|
||||
bottom: 33px;
|
||||
bottom: 26px;
|
||||
margin-bottom: -$o-mail-emoji-height;
|
||||
|
||||
* {
|
||||
@@ -50,15 +41,12 @@ $o-mail-emoji-height: 2rem;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.o_mail_emojis_dropdown_textarea{
|
||||
bottom: 40px;
|
||||
.o_mail_emojis_dropdown_textarea {
|
||||
bottom: 30px;
|
||||
}
|
||||
|
||||
|
||||
.o_xxs_form_view {
|
||||
.o_mail_emojis_dropdown {
|
||||
bottom: 50px;
|
||||
}
|
||||
.o_mail_add_emoji {
|
||||
.dropdown-menu {
|
||||
max-width: 320px;
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { CharField } from "@web/views/fields/char/char_field";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import MailEmojisMixin from '@mail/js/emojis_mixin';
|
||||
import EmojisFieldCommon from '@mail/views/fields/emojis_field_common';
|
||||
import { EmojisDropdown } from '@mail/js/emojis_dropdown';
|
||||
import { EmojisFieldCommon } from '@mail/views/fields/emojis_field_common';
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
import basicFields from 'web.basic_fields';
|
||||
import registry from 'web.field_registry';
|
||||
const { useRef } = owl;
|
||||
|
||||
/**
|
||||
* Extension of the FieldChar that will add emojis support
|
||||
*/
|
||||
var EmojisCharField = basicFields.FieldChar.extend(MailEmojisMixin, EmojisFieldCommon);
|
||||
export class EmojisCharField extends CharField {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.targetEditElement = useRef('input');
|
||||
this._setupOverride();
|
||||
}
|
||||
};
|
||||
|
||||
registry.add('char_emojis', EmojisCharField);
|
||||
|
||||
export default EmojisCharField;
|
||||
patch(EmojisCharField.prototype, 'emojis_char_field_mail_mixin', MailEmojisMixin);
|
||||
patch(EmojisCharField.prototype, 'emojis_char_field_field_mixin', EmojisFieldCommon);
|
||||
EmojisCharField.template = 'mail.EmojisCharField';
|
||||
EmojisCharField.components = { ...CharField.components, EmojisDropdown };
|
||||
EmojisCharField.additionalClasses = [...(CharField.additionalClasses || []), 'o_field_text'];
|
||||
registry.category("fields").add("char_emojis", EmojisCharField);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.EmojisCharField" t-inherit="web.CharField" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//span[1]" position="attributes">
|
||||
<attribute name="t-ref">targetReadonlyElement</attribute>
|
||||
</xpath>
|
||||
<xpath expr="/*[last()]/*[last()]" position="after">
|
||||
<EmojisDropdown onEmojiClick="onEmojiClick" readonly="props.readonly" type="'char'"/>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -1,144 +1,39 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { debounce } from "@web/core/utils/timing";
|
||||
import emojis from '@mail/js/emojis';
|
||||
import MailEmojisMixin from '@mail/js/emojis_mixin';
|
||||
import core from 'web.core';
|
||||
|
||||
var _onEmojiClickMixin = MailEmojisMixin._onEmojiClick;
|
||||
var QWeb = core.qweb;
|
||||
const _onEmojiClickMixin = MailEmojisMixin.onEmojiClick;
|
||||
const { useRef, onMounted } = owl;
|
||||
|
||||
/*
|
||||
* Common code for FieldTextEmojis and FieldCharEmojis
|
||||
* Common code for EmojisTextField and EmojisCharField
|
||||
*/
|
||||
var EmojisFieldCommon = {
|
||||
/**
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
init: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (this.nodeOptions.keydown_debounce_delay === undefined) {
|
||||
this.nodeOptions.keydown_debounce_delay = 2000;
|
||||
}
|
||||
this._triggerOnchange = debounce(this._triggerOnchange, this.nodeOptions.keydown_debounce_delay);
|
||||
this.emojis = emojis;
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
on_attach_callback: function () {
|
||||
this._attachEmojisDropdown();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// 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);
|
||||
this._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 their message (and not only on blur).
|
||||
*
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
_onKeydown: function () {
|
||||
this._super.apply(this, arguments);
|
||||
if (this.nodeOptions.onchange_on_keydown) {
|
||||
this._triggerOnchange();
|
||||
export const EmojisFieldCommon = {
|
||||
_setupOverride() {
|
||||
this.targetReadonlyElement = useRef('targetReadonlyElement');
|
||||
this.emojisDropdown = useRef('emojisDropdown');
|
||||
if (this.props.readonly) {
|
||||
onMounted(() => {
|
||||
this.targetReadonlyElement.el.innerHTML = this._formatText(this.targetReadonlyElement.el.textContent);
|
||||
});
|
||||
}
|
||||
this.onEmojiClick = this._onEmojiClick.bind(this);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_onEmojiClick() {
|
||||
_onEmojiClickMixin.apply(this, arguments);
|
||||
this.props.update(this._getTargetTextElement().value);
|
||||
},
|
||||
/**
|
||||
* Used by MailEmojisMixin, check its document for more info.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_getTargetTextElement() {
|
||||
return this.$el;
|
||||
return this.props.readonly ? this.targetReadonlyElement.el : this.targetEditElement.el;
|
||||
},
|
||||
|
||||
/**
|
||||
* Triggers the 'change' event to refresh the value.
|
||||
* This method is debounced to run after given debouce delay on typing ends.
|
||||
* (to avoid spamming the server while the user is typing their message)
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_triggerOnchange: function () {
|
||||
this.$input.trigger('change');
|
||||
},
|
||||
|
||||
/**
|
||||
* This will add an emoji button that shows the emojis selection dropdown.
|
||||
*
|
||||
* Should be used inside '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).
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_attachEmojisDropdown: function () {
|
||||
if (!this.$emojisIcon) {
|
||||
this.$emojisIcon = $(QWeb.render('mail.EmojisDropdown', {widget: this}));
|
||||
this.$emojisIcon.find('.o_mail_emoji').on('click', this._onEmojiClick.bind(this));
|
||||
|
||||
if (this.$el.filter('span.o_field_translate').length) {
|
||||
// multi-languages activated, place the button on the left of the translation button
|
||||
this.$emojisIcon.addClass('o_mail_emojis_dropdown_translation');
|
||||
}
|
||||
if (this.$el.filter('textarea').length) {
|
||||
this.$emojisIcon.addClass('o_mail_emojis_dropdown_textarea');
|
||||
}
|
||||
this.$el.last().after(this.$emojisIcon);
|
||||
}
|
||||
|
||||
if (this.mode === 'edit') {
|
||||
this.$emojisIcon.show();
|
||||
} else {
|
||||
this.$emojisIcon.hide();
|
||||
}
|
||||
},
|
||||
|
||||
destroy() {
|
||||
this._super(...arguments);
|
||||
if (this.$emojisIcon) {
|
||||
this.$emojisIcon.remove();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default EmojisFieldCommon;
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { TextField } from "@web/views/fields/text/text_field";
|
||||
import { patch } from "@web/core/utils/patch";
|
||||
import MailEmojisMixin from '@mail/js/emojis_mixin';
|
||||
import EmojisFieldCommon from '@mail/views/fields/emojis_field_common';
|
||||
|
||||
import basicFields from 'web.basic_fields';
|
||||
import registry from 'web.field_registry';
|
||||
import { EmojisDropdown } from '@mail/js/emojis_dropdown';
|
||||
import { EmojisFieldCommon } from '@mail/views/fields/emojis_field_common';
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
/**
|
||||
* Extension of the FieldText that will add emojis support
|
||||
*/
|
||||
var EmojisTextField = basicFields.FieldText.extend(MailEmojisMixin, EmojisFieldCommon);
|
||||
export class EmojisTextField extends TextField {
|
||||
setup() {
|
||||
super.setup();
|
||||
this.targetEditElement = this.textareaRef;
|
||||
this._setupOverride();
|
||||
}
|
||||
};
|
||||
|
||||
registry.add('text_emojis', EmojisTextField);
|
||||
|
||||
export default EmojisTextField;
|
||||
patch(EmojisTextField.prototype, 'emojis_text_field_mail_mixin', MailEmojisMixin);
|
||||
patch(EmojisTextField.prototype, 'emojis_text_field_field_mixin', EmojisFieldCommon);
|
||||
EmojisTextField.template = 'mail.EmojisTextField';
|
||||
EmojisTextField.components = { ...TextField.components, EmojisDropdown };
|
||||
EmojisTextField.additionalClasses = [...(TextField.additionalClasses || []), 'o_field_text'];
|
||||
registry.category("fields").add("text_emojis", EmojisTextField);
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="mail.EmojisTextField" t-inherit="web.TextField" t-inherit-mode="primary" owl="1">
|
||||
<xpath expr="//span[1]" position="attributes">
|
||||
<attribute name="t-ref">targetReadonlyElement</attribute>
|
||||
</xpath>
|
||||
<xpath expr="/*[last()]/*[last()]" position="after">
|
||||
<EmojisDropdown onEmojiClick="onEmojiClick" type="'text'"
|
||||
enable_emojis="props.enable_emojis" readonly="props.readonly"/>
|
||||
</xpath>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates xml:space="preserve">
|
||||
|
||||
<div t-name="mail.legacy.Composer.emojis" class="o_mail_emoji_container">
|
||||
<t t-foreach="emojis" t-as="emoji">
|
||||
<span t-att-data-emoji="emoji.sources[0]" class="o_mail_emoji" t-att-title="emoji.name" t-att-aria-label="emoji.name">
|
||||
<t t-out="emoji.codepoints"/>
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
</templates>
|
||||
@@ -1,7 +1,12 @@
|
||||
<?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 position-relative">
|
||||
<button class="btn btn-block dropdown-toggle px-3 py-1"
|
||||
<t t-name="mail.EmojisDropdown" owl="1">
|
||||
<div t-ref="emojisDropdown" t-att-class="{
|
||||
'd-none': props.readonly || props.enable_emojis === false,
|
||||
'o_mail_emojis_dropdown_translation': props.readonly and props.isTranslatable,
|
||||
'o_mail_emojis_dropdown_textarea': props.type === 'text' and !props.readonly,
|
||||
}" class="o_mail_emojis_dropdown o_mail_add_emoji dropdown position-relative">
|
||||
<button class="btn btn-block dropdown-toggle px-3 py-1" t-ref="toggleRef"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-haspopup="true"
|
||||
@@ -9,10 +14,18 @@
|
||||
title="Add an emoji">
|
||||
<i class="fa fa-smile-o"/>
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-end border-0 p-2" style="width: 406px">
|
||||
<t t-call="mail.legacy.Composer.emojis">
|
||||
<t t-set="emojis" t-value="widget.emojis"></t>
|
||||
</t>
|
||||
<div class="dropdown-menu border-0 p-2" style="width: 406px">
|
||||
<div class="o_mail_emoji_container">
|
||||
<t t-foreach="emojis" t-as="emoji" t-key="emoji_index">
|
||||
<span t-on-click="props.onEmojiClick"
|
||||
t-att-data-emoji="emoji.sources[0]" class="o_mail_emoji"
|
||||
t-att-title="emoji.name or emoji.description"
|
||||
t-att-aria-label="emoji.name or emoji.description">
|
||||
<t t-out="emoji.codepoints or emoji.unicode"/>
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -44,7 +44,7 @@ odoo.define('mass_mailing.mass_mailing_editor_tour', function (require) {
|
||||
trigger: '[name="body_arch"] iframe .o_editable h1',
|
||||
run: () => {},
|
||||
}, {
|
||||
trigger: 'input[name="subject"]',
|
||||
trigger: 'input#subject',
|
||||
run: 'text Test',
|
||||
}, {
|
||||
trigger: '.o_form_view', // blur previous input
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
.o_form_view .o_mass_mailing_mailing_form,
|
||||
.o_legacy_form_view.o_mass_mailing_mailing_form {
|
||||
.o_mass_mailing_subject {
|
||||
&.o_form_readonly .o_mass_mailing_subject {
|
||||
// Place the favorite button without breaking the emoji widget
|
||||
padding-right: 50px;
|
||||
.o_mass_mailing_favorite {
|
||||
@@ -68,7 +68,7 @@
|
||||
margin-left: -40px;
|
||||
bottom: 0;
|
||||
}
|
||||
span.o_field_char {
|
||||
div.o_field_char_emojis {
|
||||
// Override widget CSS attributes to place
|
||||
// the favorite button next to the subject
|
||||
margin-right: 1rem;
|
||||
|
||||
Reference in New Issue
Block a user