diff --git a/addons/hr_expense/static/src/mixins/document_upload.js b/addons/hr_expense/static/src/mixins/document_upload.js index bbcf300bc64..aa0be016e3f 100644 --- a/addons/hr_expense/static/src/mixins/document_upload.js +++ b/addons/hr_expense/static/src/mixins/document_upload.js @@ -4,9 +4,9 @@ import { useBus, useService } from '@web/core/utils/hooks'; const { useRef, useEffect, useState } = owl; -export const ExpenseDocumentDropZone = { +export const ExpenseDocumentDropZone = (T) => class ExpenseDocumentDropZone extends T { setup() { - this._super(); + super.setup(); this.dragState = useState({ showDragZone: false, }); @@ -31,31 +31,31 @@ export const ExpenseDocumentDropZone = { }, () => [document.querySelector('.o_content')] ); - }, + } highlight(ev) { ev.stopPropagation(); ev.preventDefault(); this.dragState.showDragZone = true; - }, + } unhighlight(ev) { ev.stopPropagation(); ev.preventDefault(); this.dragState.showDragZone = false; - }, + } async onDrop(ev) { ev.preventDefault(); await this.env.bus.trigger("change_file_input", { files: ev.dataTransfer.files, }); - }, + } }; -export const ExpenseDocumentUpload = { +export const ExpenseDocumentUpload = (T) => class ExpenseDocumentUpload extends T { setup() { - this._super(); + super.setup(); this.actionService = useService('action'); this.notification = useService('notification'); this.orm = useService('orm'); @@ -67,11 +67,11 @@ export const ExpenseDocumentUpload = { this.fileInput.el.files = ev.detail.files; await this.onChangeFileInput(); }); - }, + } uploadDocument() { this.fileInput.el.click(); - }, + } async onChangeFileInput() { const params = { @@ -87,7 +87,7 @@ export const ExpenseDocumentUpload = { throw new Error(attachments.error); } this.onUpload(attachments); - }, + } async onUpload(attachments) { const attachmentIds = attachments.map((a) => a.id); @@ -100,5 +100,5 @@ export const ExpenseDocumentUpload = { const action = await this.orm.call('hr.expense', 'create_expense_from_attachments', ["", attachmentIds]); this.actionService.doAction(action); - }, + } }; diff --git a/addons/hr_expense/static/src/mixins/qrcode.js b/addons/hr_expense/static/src/mixins/qrcode.js index 8d7bd395f0f..6c398ed9fc4 100644 --- a/addons/hr_expense/static/src/mixins/qrcode.js +++ b/addons/hr_expense/static/src/mixins/qrcode.js @@ -4,15 +4,15 @@ import { useService } from "@web/core/utils/hooks"; const { onMounted, onPatched, useRef } = owl; -export const ExpenseMobileQRCode = { +export const ExpenseMobileQRCode = (T) => class ExpenseMobileQRCode extends T { setup() { - this._super(); + super.setup(); this.root = useRef('root'); this.actionService = useService('action'); onMounted(this.bindAppsIcons); onPatched(this.bindAppsIcons); - }, + } bindAppsIcons() { const apps = this.root.el.querySelectorAll('.o_expense_mobile_app'); @@ -24,7 +24,7 @@ export const ExpenseMobileQRCode = { for (const app of apps) { app.addEventListener('click', handler); } - }, + } handleClick(ev) { ev.preventDefault(); diff --git a/addons/hr_expense/static/src/views/kanban.js b/addons/hr_expense/static/src/views/kanban.js index 8b56bcbc758..b9b7f15d535 100644 --- a/addons/hr_expense/static/src/views/kanban.js +++ b/addons/hr_expense/static/src/views/kanban.js @@ -1,7 +1,6 @@ /** @odoo-module */ import { registry } from '@web/core/registry'; -import { patch } from '@web/core/utils/patch'; import { ExpenseDashboard } from '../components/expense_dashboard'; import { ExpenseMobileQRCode } from '../mixins/qrcode'; @@ -11,12 +10,9 @@ import { kanbanView } from '@web/views/kanban/kanban_view'; import { KanbanController } from '@web/views/kanban/kanban_controller'; import { KanbanRenderer } from '@web/views/kanban/kanban_renderer'; -export class ExpenseKanbanController extends KanbanController {} -patch(ExpenseKanbanController.prototype, 'expense_kanban_controller_upload', ExpenseDocumentUpload); +export class ExpenseKanbanController extends ExpenseDocumentUpload(KanbanController) {} -export class ExpenseKanbanRenderer extends KanbanRenderer {} -patch(ExpenseKanbanRenderer.prototype, 'expense_kanban_renderer_qrcode', ExpenseMobileQRCode); -patch(ExpenseKanbanRenderer.prototype, 'expense_kanban_renderer_qrcode_dzone', ExpenseDocumentDropZone); +export class ExpenseKanbanRenderer extends ExpenseDocumentDropZone(ExpenseMobileQRCode(KanbanRenderer)) {} ExpenseKanbanRenderer.template = 'hr_expense.KanbanRenderer'; export class ExpenseDashboardKanbanRenderer extends ExpenseKanbanRenderer {} diff --git a/addons/hr_expense/static/src/views/list.js b/addons/hr_expense/static/src/views/list.js index b6b8ad307be..c9a4778162d 100644 --- a/addons/hr_expense/static/src/views/list.js +++ b/addons/hr_expense/static/src/views/list.js @@ -5,7 +5,6 @@ import { ExpenseMobileQRCode } from '../mixins/qrcode'; import { ExpenseDocumentUpload, ExpenseDocumentDropZone } from '../mixins/document_upload'; import { registry } from '@web/core/registry'; -import { patch } from '@web/core/utils/patch'; import { useService } from '@web/core/utils/hooks'; import { listView } from "@web/views/list/list_view"; @@ -14,7 +13,7 @@ import { ListRenderer } from "@web/views/list/list_renderer"; const { onWillStart } = owl; -export class ExpenseListController extends ListController { +export class ExpenseListController extends ExpenseDocumentUpload(ListController) { setup() { super.setup(); this.orm = useService('orm'); @@ -81,11 +80,8 @@ export class ExpenseListController extends ListController { } } } -patch(ExpenseListController.prototype, 'expense_list_controller_upload', ExpenseDocumentUpload); -export class ExpenseListRenderer extends ListRenderer {} -patch(ExpenseListRenderer.prototype, 'expense_list_renderer_qrcode', ExpenseMobileQRCode); -patch(ExpenseListRenderer.prototype, 'expense_list_renderer_qrcode_dzone', ExpenseDocumentDropZone); +export class ExpenseListRenderer extends ExpenseDocumentDropZone(ExpenseMobileQRCode(ListRenderer)) {} ExpenseListRenderer.template = 'hr_expense.ListRenderer'; export class ExpenseDashboardListRenderer extends ExpenseListRenderer {} diff --git a/addons/lunch/static/src/mixins/lunch_renderer_mixin.js b/addons/lunch/static/src/mixins/lunch_renderer_mixin.js index b27b8fd57c4..dee5d86372e 100644 --- a/addons/lunch/static/src/mixins/lunch_renderer_mixin.js +++ b/addons/lunch/static/src/mixins/lunch_renderer_mixin.js @@ -2,13 +2,13 @@ import { useBus, useService } from "@web/core/utils/hooks"; -export const LunchRendererMixin = { +export const LunchRendererMixin = (T) => class LunchRendererMixin extends T { setup() { - this._super(...arguments); + super.setup(...arguments); this.action = useService("action"); useBus(this.env.bus, 'lunch_open_order', (ev) => this.openOrderLine(ev.detail.productId)); - }, + } openOrderLine(productId, orderId) { let context = {}; @@ -36,5 +36,5 @@ export const LunchRendererMixin = { this.action.doAction(action, { onClose: () => this.env.bus.trigger('lunch_update_dashboard') }); - }, -} + } +}; diff --git a/addons/lunch/static/src/views/kanban.js b/addons/lunch/static/src/views/kanban.js index 2f49d27fef3..a3d67523a05 100644 --- a/addons/lunch/static/src/views/kanban.js +++ b/addons/lunch/static/src/views/kanban.js @@ -1,6 +1,5 @@ /** @odoo-module */ -import { patch } from '@web/core/utils/patch'; import { registry } from '@web/core/registry'; import { kanbanView } from '@web/views/kanban/kanban_view'; @@ -19,8 +18,7 @@ export class LunchKanbanRecord extends KanbanRecord { } } -export class LunchKanbanRenderer extends KanbanRenderer {} -patch(LunchKanbanRenderer.prototype, 'lunch_kanban_renderer_mixin', LunchRendererMixin); +export class LunchKanbanRenderer extends LunchRendererMixin(KanbanRenderer) {} LunchKanbanRenderer.template = 'lunch.KanbanRenderer'; LunchKanbanRenderer.components = { diff --git a/addons/lunch/static/src/views/list.js b/addons/lunch/static/src/views/list.js index 09661d84592..b3767ab14e9 100644 --- a/addons/lunch/static/src/views/list.js +++ b/addons/lunch/static/src/views/list.js @@ -1,6 +1,5 @@ /** @odoo-module */ -import { patch } from '@web/core/utils/patch'; import { registry } from '@web/core/registry'; import { listView } from '@web/views/list/list_view'; @@ -12,12 +11,11 @@ import { LunchRendererMixin } from '../mixins/lunch_renderer_mixin'; import { LunchSearchModel } from './search_model'; -export class LunchListRenderer extends ListRenderer { +export class LunchListRenderer extends LunchRendererMixin(ListRenderer) { onCellClicked(record, column) { this.openOrderLine(record.resId); } } -patch(LunchListRenderer.prototype, 'lunch_list_renderer_mixin', LunchRendererMixin); LunchListRenderer.template = 'lunch.ListRenderer'; LunchListRenderer.components = { diff --git a/addons/mail/static/src/js/emojis_mixin.js b/addons/mail/static/src/js/emojis_mixin.js index 4edea5f5670..6d30188564e 100644 --- a/addons/mail/static/src/js/emojis_mixin.js +++ b/addons/mail/static/src/js/emojis_mixin.js @@ -3,36 +3,24 @@ import { escape } from "@web/core/utils/strings"; /** - * This mixin gathers a few methods that are used to handle emojis. + * Adds a span with a CSS class around chains of emojis in the message for styling purposes. + * The input is first passed through 'escape' to prevent unwanted injections into the HTML * - * It's currently used to format text and wrap the emojis around to make them look nicer + * Sequences of emojis are wrapped instead of individual ones to prevent compound emojis + * such as ๐Ÿ‘ฉ๐Ÿฟ = ๐Ÿ‘ฉ + ๐Ÿฟ [dark skin tone character] from being separated. * + * This will only match characters that have a different presentation from normal text, unlike ยฎ + * For alternatives, see: https://www.unicode.org/reports/tr51/#Emoji_Properties_and_Data_Files + * + * @param {String} message a text message to format */ -export default { - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- +export function formatText(message) { + message = escape(message); + message = message.replaceAll( + /(\p{Emoji_Presentation}+)/gu, + "$1" + ); + message = message.replace(/(?:\r\n|\r|\n)/g, "
"); - /** - * Adds a span with a CSS class around chains of emojis in the message for styling purposes. - * The input is first passed through 'escape' to prevent unwanted injections into the HTML - * - * Sequences of emojis are wrapped instead of individual ones to prevent compound emojis - * such as ๐Ÿ‘ฉ๐Ÿฟ = ๐Ÿ‘ฉ + ๐Ÿฟ [dark skin tone character] from being separated. - * - * This will only match characters that have a different presentation from normal text, unlike ยฎ - * For alternatives, see: https://www.unicode.org/reports/tr51/#Emoji_Properties_and_Data_Files - * - * @param {String} message a text message to format - */ - _formatText(message) { - message = escape(message); - message = message.replaceAll( - /(\p{Emoji_Presentation}+)/gu, - "$1" - ); - message = message.replace(/(?:\r\n|\r|\n)/g, "
"); - - return message; - }, -}; + return message; +} diff --git a/addons/mail/static/src/views/web/fields/emojis_char_field/emojis_char_field.js b/addons/mail/static/src/views/web/fields/emojis_char_field/emojis_char_field.js index a94558cbea0..a4c0f1b968f 100644 --- a/addons/mail/static/src/views/web/fields/emojis_char_field/emojis_char_field.js +++ b/addons/mail/static/src/views/web/fields/emojis_char_field/emojis_char_field.js @@ -1,18 +1,16 @@ /* @odoo-module */ -import MailEmojisMixin from "@mail/js/emojis_mixin"; import { EmojisFieldCommon } from "@mail/views/web/fields/emojis_field_common/emojis_field_common"; import { useRef } from "@odoo/owl"; import { registry } from "@web/core/registry"; -import { patch } from "@web/core/utils/patch"; import { CharField, charField } from "@web/views/fields/char/char_field"; /** * Extension of the FieldChar that will add emojis support */ -export class EmojisCharField extends CharField { +export class EmojisCharField extends EmojisFieldCommon(CharField) { setup() { super.setup(); this.targetEditElement = useRef("input"); @@ -20,8 +18,6 @@ export class EmojisCharField extends CharField { } } -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 }; diff --git a/addons/mail/static/src/views/web/fields/emojis_field_common/emojis_field_common.js b/addons/mail/static/src/views/web/fields/emojis_field_common/emojis_field_common.js index 37bdb7727d4..7af8afa321b 100644 --- a/addons/mail/static/src/views/web/fields/emojis_field_common/emojis_field_common.js +++ b/addons/mail/static/src/views/web/fields/emojis_field_common/emojis_field_common.js @@ -7,7 +7,7 @@ import { useRef } from "@odoo/owl"; /* * Common code for EmojisTextField and EmojisCharField */ -export const EmojisFieldCommon = { +export const EmojisFieldCommon = (T) => class EmojisFieldCommon extends T { /** * Create an emoji textfield view to enable opening an emoji popover */ @@ -38,5 +38,5 @@ export const EmojisFieldCommon = { position: "bottom", } ); - }, + } }; diff --git a/addons/mail/static/src/views/web/fields/emojis_text_field/emojis_text_field.js b/addons/mail/static/src/views/web/fields/emojis_text_field/emojis_text_field.js index dbb809b476e..6bedcf7c5e6 100644 --- a/addons/mail/static/src/views/web/fields/emojis_text_field/emojis_text_field.js +++ b/addons/mail/static/src/views/web/fields/emojis_text_field/emojis_text_field.js @@ -1,16 +1,14 @@ /* @odoo-module */ -import MailEmojisMixin from "@mail/js/emojis_mixin"; import { EmojisFieldCommon } from "@mail/views/web/fields/emojis_field_common/emojis_field_common"; import { registry } from "@web/core/registry"; -import { patch } from "@web/core/utils/patch"; import { TextField, textField } from "@web/views/fields/text/text_field"; /** * Extension of the FieldText that will add emojis support */ -export class EmojisTextField extends TextField { +export class EmojisTextField extends EmojisFieldCommon(TextField) { setup() { super.setup(); this.targetEditElement = this.textareaRef; @@ -18,8 +16,6 @@ export class EmojisTextField extends TextField { } } -patch(EmojisTextField.prototype, "emojis_char_field_mail_mixin", MailEmojisMixin); -patch(EmojisTextField.prototype, "emojis_text_field_field_mixin", EmojisFieldCommon); EmojisTextField.template = "mail.EmojisTextField"; EmojisTextField.components = { ...TextField.components }; diff --git a/addons/mail/static/src/views/web/fields/many2many_avatar_user_field/many2many_avatar_user_field.js b/addons/mail/static/src/views/web/fields/many2many_avatar_user_field/many2many_avatar_user_field.js index 0b29e9be0be..eb9d959b0a9 100644 --- a/addons/mail/static/src/views/web/fields/many2many_avatar_user_field/many2many_avatar_user_field.js +++ b/addons/mail/static/src/views/web/fields/many2many_avatar_user_field/many2many_avatar_user_field.js @@ -5,7 +5,6 @@ import { useAssignUserCommand } from "@mail/views/web/fields/assign_user_command import { registry } from "@web/core/registry"; import { TagsList } from "@web/core/tags_list/tags_list"; -import { patch } from "@web/core/utils/patch"; import { usePopover } from "@web/core/popover/popover_hook"; import { browser } from "@web/core/browser/browser"; import { AvatarCardPopover } from "@mail/discuss/web/avatar_card/avatar_card_popover"; @@ -22,9 +21,9 @@ import { export class Many2ManyAvatarUserTagsList extends TagsList {} Many2ManyAvatarUserTagsList.template = "mail.Many2ManyAvatarUserTagsList"; -const userChatter = { +const WithUserChatter = (T) => class UserChatterMixin extends T { setup() { - this._super(...arguments); + super.setup(...arguments); this.openChat = useOpenChat(this.relation); if (this.props.withCommand) { useAssignUserCommand(); @@ -34,11 +33,11 @@ const userChatter = { }); this.openTimeout = false; this.lastOpenedId = 0; - }, + } getTagProps(record) { return { - ...this._super(...arguments), + ...super.getTagProps(...arguments), onImageClicked: () => this.openChat(record.resId), openCard: (ev) => { if (this.env.isSmall || this.relation !== "res.users") { @@ -62,15 +61,15 @@ const userChatter = { delete this.openTimeout; }, }; - }, -}; -export class Many2ManyTagsAvatarUserField extends Many2ManyTagsAvatarField { + } +} + +export class Many2ManyTagsAvatarUserField extends WithUserChatter(Many2ManyTagsAvatarField) { static components = { ...Many2ManyTagsAvatarField.components, TagsList: Many2ManyAvatarUserTagsList, }; } -patch(Many2ManyTagsAvatarUserField.prototype, "mail/fields/web", userChatter); export const many2ManyTagsAvatarUserField = { ...many2ManyTagsAvatarField, @@ -84,7 +83,7 @@ export class KanbanMany2ManyAvatarUserTagsList extends KanbanMany2ManyTagsAvatar static template = "mail.KanbanMany2ManyAvatarUserTagsList"; } -export class KanbanMany2ManyTagsAvatarUserField extends KanbanMany2ManyTagsAvatarField { +export class KanbanMany2ManyTagsAvatarUserField extends WithUserChatter(KanbanMany2ManyTagsAvatarField) { static template = "mail.KanbanMany2ManyTagsAvatarUserField"; static components = { ...KanbanMany2ManyTagsAvatarField.components, @@ -94,7 +93,6 @@ export class KanbanMany2ManyTagsAvatarUserField extends KanbanMany2ManyTagsAvata return !this.props.readonly; } } -patch(KanbanMany2ManyTagsAvatarUserField.prototype, "mail/fields/web", userChatter); export const kanbanMany2ManyTagsAvatarUserField = { ...kanbanMany2ManyTagsAvatarField, component: KanbanMany2ManyTagsAvatarUserField, @@ -102,7 +100,7 @@ export const kanbanMany2ManyTagsAvatarUserField = { }; registry.category("fields").add("kanban.many2many_avatar_user", kanbanMany2ManyTagsAvatarUserField); -export class ListMany2ManyTagsAvatarUserField extends ListMany2ManyTagsAvatarField { +export class ListMany2ManyTagsAvatarUserField extends WithUserChatter(ListMany2ManyTagsAvatarField) { static template = "mail.ListMany2ManyTagsAvatarUserField"; static components = { ...ListMany2ManyTagsAvatarField.components, @@ -113,7 +111,6 @@ export class ListMany2ManyTagsAvatarUserField extends ListMany2ManyTagsAvatarFie return this.props.record.data[this.props.name].records.length === 1 || !this.props.readonly; } } -patch(ListMany2ManyTagsAvatarUserField.prototype, "mail/fields/web", userChatter); export const listMany2ManyTagsAvatarUserField = { ...listMany2ManyTagsAvatarField, diff --git a/addons/mail/static/src/views/web/fields/many2one_avatar_user_field/many2one_avatar_user_field.js b/addons/mail/static/src/views/web/fields/many2one_avatar_user_field/many2one_avatar_user_field.js index 5a4bf43563b..0705e26e2b7 100644 --- a/addons/mail/static/src/views/web/fields/many2one_avatar_user_field/many2one_avatar_user_field.js +++ b/addons/mail/static/src/views/web/fields/many2one_avatar_user_field/many2one_avatar_user_field.js @@ -5,7 +5,6 @@ import { useAssignUserCommand } from "@mail/views/web/fields/assign_user_command import { _t } from "@web/core/l10n/translation"; import { registry } from "@web/core/registry"; -import { patch } from "@web/core/utils/patch"; import { Many2OneAvatarField, many2OneAvatarField, @@ -16,9 +15,9 @@ import { usePopover } from "@web/core/popover/popover_hook"; import { browser } from "@web/core/browser/browser"; import { AvatarCardPopover } from "@mail/discuss/web/avatar_card/avatar_card_popover"; -const userChatter = { +const WithUserChatter = (T) => class extends T { setup() { - this._super(...arguments); + super.setup(...arguments); this.openChat = useOpenChat(this.relation); if (this.props.withCommand) { useAssignUserCommand(); @@ -27,14 +26,14 @@ const userChatter = { closeOnHoverAway: true, }); this.openTimeout = false; - }, + } onClickAvatar() { const id = this.props.record.data[this.props.name][0] ?? false; if (id !== false) { this.openChat(id); } - }, + } openCard(ev) { if (this.env.isSmall || this.relation !== "res.users") { @@ -51,15 +50,15 @@ const userChatter = { }); } }, 350); - }, + } clearTimeout() { browser.clearTimeout(this.openTimeout); delete this.openTimeout; - }, + } }; -export class Many2OneAvatarUserField extends Many2OneAvatarField { +export class Many2OneAvatarUserField extends WithUserChatter(Many2OneAvatarField) { static template = "mail.Many2OneAvatarUserField"; static props = { ...Many2OneAvatarField.props, @@ -68,7 +67,6 @@ export class Many2OneAvatarUserField extends Many2OneAvatarField { withCommand: { type: Boolean, optional: true }, }; } -patch(Many2OneAvatarUserField.prototype, "mail/fields/web", userChatter); export const many2OneAvatarUserField = { ...many2OneAvatarField, @@ -85,7 +83,7 @@ export const many2OneAvatarUserField = { registry.category("fields").add("many2one_avatar_user", many2OneAvatarUserField); -export class KanbanMany2OneAvatarUserField extends KanbanMany2OneAvatarField { +export class KanbanMany2OneAvatarUserField extends WithUserChatter(KanbanMany2OneAvatarField) { static template = "mail.KanbanMany2OneAvatarUserField"; static props = { ...KanbanMany2OneAvatarField.props, @@ -101,7 +99,6 @@ export class KanbanMany2OneAvatarUserField extends KanbanMany2OneAvatarField { return props; } } -patch(KanbanMany2OneAvatarUserField.prototype, "mail/fields/web", userChatter); export const kanbanMany2OneAvatarUserField = { ...kanbanMany2OneAvatarField, diff --git a/addons/mail/static/tests/emoji/emojis_mixin_tests.js b/addons/mail/static/tests/emoji/emojis_mixin_tests.js index d46b2cf1390..7eefc8ef83a 100644 --- a/addons/mail/static/tests/emoji/emojis_mixin_tests.js +++ b/addons/mail/static/tests/emoji/emojis_mixin_tests.js @@ -1,6 +1,6 @@ /* @odoo-module */ -import EmojiMixin from "@mail/js/emojis_mixin"; +import { formatText } from "@mail/js/emojis_mixin"; QUnit.module("emojis mixin"); @@ -8,5 +8,5 @@ QUnit.test("Emoji formatter handles compound emojis", (assert) => { const testString = "๐Ÿ‘ฉ๐Ÿฟtest๐Ÿ‘ฉ๐Ÿฟ๐Ÿ‘ฉt๐Ÿ‘ฉ"; const expectedString = "๐Ÿ‘ฉ๐Ÿฟtest๐Ÿ‘ฉ๐Ÿฟ๐Ÿ‘ฉt๐Ÿ‘ฉ"; - assert.deepEqual(EmojiMixin._formatText(testString), expectedString); + assert.deepEqual(formatText(testString), expectedString); });