From 614de869892fd2623311ac1fae5c76ee06a356c7 Mon Sep 17 00:00:00 2001 From: "Pulinckx Pierre (PIPU)" Date: Thu, 13 Apr 2023 08:46:00 +0000 Subject: [PATCH] [REF] *: Replace underscore function by native JS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace _.isNumber(), _.filter(), _.reject(), _.unique(), _.indexOf(), _.lastIndexOf(), _.findIndex(), _.range() _.keys(), _.values(), _.str.sprintf() and some _.each() closes odoo/odoo#118003 Signed-off-by: Géry Debongnie --- .../static/src/js/password_meter.js | 3 +- .../calendar/static/src/js/base_calendar.js | 2 +- .../static/src/js/tours/crm_iap_lead.js | 2 +- .../static/src/components/qrcode_action.js | 3 +- .../legacy/legacy_models/public_livechat.js | 6 +- .../src/legacy/widgets/feedback/feedback.js | 2 +- addons/l10n_ar/static/src/js/tours/account.js | 4 +- addons/l10n_fr_pos_cert/static/src/js/pos.js | 7 +- .../static/src/js/mailing_portal.js | 3 +- .../static/src/js/mass_mailing_html_field.js | 2 +- .../src/js/mass_mailing_link_dialog_fix.js | 4 +- .../static/src/app/navbar/navbar.js | 5 +- .../static/src/js/Popups/ClosePosPopup.js | 3 +- .../PartnerListScreen/PartnerListScreen.js | 14 +-- .../js/Screens/PaymentScreen/PaymentScreen.js | 3 +- .../ControlButtons/SaveButton.js | 19 +--- .../js/Screens/ProductScreen/ProductScreen.js | 9 +- .../Screens/ProductScreen/ProductsWidget.js | 38 +++++-- .../Screens/TicketScreen/OrderlineDetails.js | 5 +- .../js/Screens/TicketScreen/TicketScreen.js | 36 +++--- addons/point_of_sale/static/src/js/db.js | 12 +- addons/point_of_sale/static/src/js/models.js | 42 +++++-- .../static/tests/tours/pricelist_tour.js | 2 +- addons/portal/static/src/js/portal_chatter.js | 10 +- .../portal/static/src/js/portal_composer.js | 5 +- addons/portal/static/src/js/portal_sidebar.js | 5 +- .../static/src/js/portal_chatter.js | 6 +- .../pos_adyen/static/src/js/payment_adyen.js | 11 +- .../static/src/js/DiscountButton.js | 3 +- .../static/src/js/printers.js | 44 ++++--- .../pos_loyalty/static/src/js/PartnerLine.js | 3 +- .../static/src/js/ProductScreen.js | 3 +- .../ControlButtons/TableGuestsButton.js | 3 +- .../SaleOrderManagementScreen.js | 2 +- .../static/src/js/payment_stripe.js | 5 +- addons/sale/static/src/js/variant_mixin.js | 5 +- .../static/src/list/list_functions.js | 3 +- addons/survey/static/src/js/survey_form.js | 8 +- .../src/js/survey_session_leaderboard.js | 2 +- .../static/src/js/survey_session_manage.js | 9 +- .../core/debug/profiling/profiling_qweb.js | 2 +- addons/web/static/src/core/utils/numbers.js | 17 +++ .../frontend/keyboard_navigation_mixin.js | 4 +- .../legacy/js/control_panel/control_panel.js | 3 +- .../web/static/src/legacy/js/core/dialog.js | 2 +- .../web/static/src/legacy/js/core/domain.js | 34 +++--- .../web/static/src/legacy/js/core/mixins.js | 4 +- .../static/src/legacy/js/core/ram_storage.js | 2 +- addons/web/static/src/legacy/js/core/time.js | 5 +- .../static/src/legacy/js/core/translation.js | 2 +- addons/web/static/src/legacy/js/core/utils.js | 3 +- .../src/legacy/js/fields/basic_fields.js | 5 +- .../src/legacy/js/fields/field_utils.js | 39 +++---- .../src/legacy/js/fields/relational_fields.js | 15 +-- .../src/legacy/js/views/basic/basic_model.js | 107 +++++++++--------- .../static/src/legacy/js/views/view_utils.js | 3 +- .../src/legacy/js/widgets/colorpicker.js | 7 +- .../src/legacy/js/widgets/domain_selector.js | 16 +-- .../widgets/model_field_selector_popover.js | 9 +- .../timezone_mismatch_field.js | 8 +- .../tests/legacy/core/py_utils_tests.js | 53 ++++++--- .../tests/legacy/helpers/mock_server.js | 32 +++--- .../tests/legacy/helpers/test_utils_mock.js | 2 +- .../views/fields/one2many_field_tests.js | 2 +- addons/web_editor/static/src/js/common/ace.js | 17 ++- .../static/src/js/editor/snippets.editor.js | 15 +-- .../static/src/js/editor/snippets.options.js | 9 +- addons/web_editor/static/tests/test_utils.js | 3 +- .../components/media_dialog/image_selector.js | 5 +- .../src/components/ace_editor/ace_editor.js | 2 +- .../static/src/js/backend/dashboard.js | 4 +- .../src/js/content/snippets.animation.js | 2 +- .../static/src/js/content/website_root.js | 2 +- .../static/src/js/editor/snippets.options.js | 3 +- .../website/static/src/js/tours/tour_utils.js | 9 +- addons/website/static/src/js/utils.js | 3 +- .../src/snippets/s_facebook_page/options.js | 4 +- .../src/snippets/s_image_gallery/options.js | 2 +- .../static/src/snippets/s_website_form/000.js | 9 +- .../src/snippets/s_website_form/options.js | 9 +- .../static/src/systray_items/new_content.js | 6 +- .../static/src/js/contentshare.js | 5 +- .../static/src/js/crm_partner_assign.js | 3 +- .../static/src/js/event_track_reminder.js | 3 +- .../static/src/js/website_forum.js | 4 +- .../static/src/js/website_links.js | 2 +- .../static/src/js/website_payment_form.js | 3 +- .../static/src/snippets/s_donation/000.js | 3 +- .../static/tests/tours/website_sale_buy.js | 4 +- .../static/src/js/portal_chatter.js | 3 +- .../src/js/slides_course_fullscreen_player.js | 11 +- .../static/src/js/slides_course_tag_add.js | 5 +- .../static/src/js/slides_upload.js | 15 +-- 93 files changed, 490 insertions(+), 404 deletions(-) diff --git a/addons/auth_password_policy_signup/static/src/js/password_meter.js b/addons/auth_password_policy_signup/static/src/js/password_meter.js index 1e6edc62188..509063186fa 100644 --- a/addons/auth_password_policy_signup/static/src/js/password_meter.js +++ b/addons/auth_password_policy_signup/static/src/js/password_meter.js @@ -3,6 +3,7 @@ import { _t } from "web.core"; import Widget from "web.Widget"; import { computeScore } from "@auth_password_policy/password_policy"; +import { sprintf } from "@web/core/utils/strings"; export default Widget.extend({ tagName: "meter", @@ -27,7 +28,7 @@ export default Widget.extend({ ); this.el.setAttribute( "title", - _.str.sprintf(helpMessage, String(this._required) || _t("no requirements")) + sprintf(helpMessage, String(this._required) || _t("no requirements")) ); return this._super().then(function () {}); }, diff --git a/addons/calendar/static/src/js/base_calendar.js b/addons/calendar/static/src/js/base_calendar.js index b9349f9a47e..7408e97ac17 100644 --- a/addons/calendar/static/src/js/base_calendar.js +++ b/addons/calendar/static/src/js/base_calendar.js @@ -12,7 +12,7 @@ BasicModel.include({ _fetchSpecialAttendeeStatus: function (record, fieldName) { var context = record.getContext({fieldName: fieldName}); var attendeeIDs = record.data[fieldName] ? this.localData[record.data[fieldName]].res_ids : []; - var meetingID = _.isNumber(record.res_id) ? record.res_id : false; + var meetingID = Number.isFinite(record.res_id) ? record.res_id : false; return this._rpc({ model: 'res.partner', method: 'get_attendee_detail', diff --git a/addons/crm_iap_mine/static/src/js/tours/crm_iap_lead.js b/addons/crm_iap_mine/static/src/js/tours/crm_iap_lead.js index 36b9c45a062..0815a347496 100644 --- a/addons/crm_iap_mine/static/src/js/tours/crm_iap_lead.js +++ b/addons/crm_iap_mine/static/src/js/tours/crm_iap_lead.js @@ -7,7 +7,7 @@ import core from "web.core"; import "@crm/js/tours/crm"; var _t = core._t; -var DragOppToWonStepIndex = _.findIndex(registry.category("web_tour.tours").get("crm_tour").steps, function (step) { +var DragOppToWonStepIndex = registry.category("web_tour.tours").get("crm_tour").steps.findIndex(step => { return (step.id === 'drag_opportunity_to_won_step'); }); diff --git a/addons/hr_expense/static/src/components/qrcode_action.js b/addons/hr_expense/static/src/components/qrcode_action.js index 3b6c270ec27..1467efd7e0f 100644 --- a/addons/hr_expense/static/src/components/qrcode_action.js +++ b/addons/hr_expense/static/src/components/qrcode_action.js @@ -2,12 +2,13 @@ import { registry } from "@web/core/registry"; import { Component, xml } from "@odoo/owl"; +import { sprintf } from "@web/core/utils/strings"; const actionRegistry = registry.category("actions"); class QRModalComponent extends Component { setup() { - this.url = _.str.sprintf( + this.url = sprintf( "/report/barcode/?barcode_type=QR&value=%s&width=256&height=256&humanreadable=1", this.props.action.params.url); } diff --git a/addons/im_livechat/static/src/legacy/legacy_models/public_livechat.js b/addons/im_livechat/static/src/legacy/legacy_models/public_livechat.js index b16366ae22e..57960c8bc41 100644 --- a/addons/im_livechat/static/src/legacy/legacy_models/public_livechat.js +++ b/addons/im_livechat/static/src/legacy/legacy_models/public_livechat.js @@ -181,7 +181,7 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, { ? [this.messaging.publicLivechatGlobal.publicLivechat.operator] : []; const sortedTypingMembers = _.sortBy(typingMembers, function (member) { - return _.indexOf(typingPartnerIDs, member.id); + return typingPartnerIDs.indexOf(member.id); }); const displayableTypingMembers = sortedTypingMembers.slice(0, 3); @@ -330,9 +330,7 @@ const PublicLivechat = Class.extend(Mixins.EventDispatcherMixin, { if (!_.contains(this._typingPartnerIDs, partnerID)) { return; } - this._typingPartnerIDs = _.reject(this._typingPartnerIDs, function (id) { - return id === partnerID; - }); + this._typingPartnerIDs = this._typingPartnerIDs.filter((id) => id !== partnerID); this._warnUpdatedTypingPartners(); }, diff --git a/addons/im_livechat/static/src/legacy/widgets/feedback/feedback.js b/addons/im_livechat/static/src/legacy/widgets/feedback/feedback.js index fc256970049..72debb0f640 100644 --- a/addons/im_livechat/static/src/legacy/widgets/feedback/feedback.js +++ b/addons/im_livechat/static/src/legacy/widgets/feedback/feedback.js @@ -92,7 +92,7 @@ const Feedback = Widget.extend({ _onClickSend() { this.$(".o_livechat_rating_reason").hide(); this._showThanksMessage(); - if (_.isNumber(this.rating)) { + if (Number.isFinite(this.rating)) { this._sendFeedback(this.$("textarea").val()); } }, diff --git a/addons/l10n_ar/static/src/js/tours/account.js b/addons/l10n_ar/static/src/js/tours/account.js index 77a915da3c8..ecc2b4e7730 100644 --- a/addons/l10n_ar/static/src/js/tours/account.js +++ b/addons/l10n_ar/static/src/js/tours/account.js @@ -4,10 +4,10 @@ import { registry } from "@web/core/registry"; let account_tour = registry.category("web_tour.tours").get("account_tour"); // Remove the step suggesting to change the name as it is done another way (document number) - account_tour.steps = _.filter(account_tour.steps, step => step.trigger != "input[name=name]"); + account_tour.steps = account_tour.steps.filter(step => step.trigger != "input[name=name]"); // Configure the AFIP Responsibility - let partner_step_idx = _.findIndex(account_tour.steps, step => step.trigger == 'div[name=partner_id] input'); + let partner_step_idx = account_tour.steps.findIndex(step => step.trigger == 'div[name=partner_id] input'); account_tour.steps.splice(partner_step_idx + 2, 0, { // FIXME WOWL: this selector needs to work in both legacy and non-legacy views trigger: "div[name=l10n_ar_afip_responsibility_type_id] input", diff --git a/addons/l10n_fr_pos_cert/static/src/js/pos.js b/addons/l10n_fr_pos_cert/static/src/js/pos.js index a54cc6377a5..9847d3c954c 100644 --- a/addons/l10n_fr_pos_cert/static/src/js/pos.js +++ b/addons/l10n_fr_pos_cert/static/src/js/pos.js @@ -3,8 +3,8 @@ import { PosGlobalState, Order, Orderline } from "@point_of_sale/js/models"; import core from "web.core"; import { patch } from "@web/core/utils/patch"; +import { sprintf } from "@web/core/utils/strings"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; - var _t = core._t; patch(PosGlobalState.prototype, "l10n_fr_pos_cert.PosGlobalState", { @@ -13,10 +13,7 @@ patch(PosGlobalState.prototype, "l10n_fr_pos_cert.PosGlobalState", { if (!this.company.country) { this.env.services.popup.add(ErrorPopup, { title: _t("Missing Country"), - body: _.str.sprintf( - _t("The company %s doesn't have a country set."), - this.company.name - ), + body: sprintf(_t("The company %s doesn't have a country set."), this.company.name), }); return false; } diff --git a/addons/mass_mailing/static/src/js/mailing_portal.js b/addons/mass_mailing/static/src/js/mailing_portal.js index 0a848e871ef..7e1d612e563 100644 --- a/addons/mass_mailing/static/src/js/mailing_portal.js +++ b/addons/mass_mailing/static/src/js/mailing_portal.js @@ -4,6 +4,7 @@ import ajax from "web.ajax"; import core from "web.core"; import "web.dom_ready"; + import { sprintf } from "@web/core/utils/strings"; var _t = core._t; @@ -45,7 +46,7 @@ if ($('.o_unsubscribe_form').length) { var unsubscribed_list = $("input[name='unsubscribed_list']").val(); if (unsubscribed_list){ - $('#subscription_info').html(_.str.sprintf( + $('#subscription_info').html(sprintf( _t("You have been successfully unsubscribed from %s."), _.escape(unsubscribed_list) )); diff --git a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js index 6395eb479ec..06638add5e9 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js @@ -268,7 +268,7 @@ export class MassMailingHtmlField extends HtmlField { this._themeClassNames = ""; const displayableThemes = device.isMobile ? - _.filter($themes, theme => !$(theme).data("hideFromMobile")) : + $themes.filter(theme => !$(theme).data("hideFromMobile")) : $themes; this._themeParams = _.map(displayableThemes, (theme) => { const $theme = $(theme); diff --git a/addons/mass_mailing/static/src/js/mass_mailing_link_dialog_fix.js b/addons/mass_mailing/static/src/js/mass_mailing_link_dialog_fix.js index c31c00bff59..ee03cdba5e3 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_link_dialog_fix.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_link_dialog_fix.js @@ -27,7 +27,7 @@ LinkDialog.include({ this.__realMMColors = {}; const $previewArea = $('
').addClass('o_mail_snippet_general'); $(this.editable).find('.o_layout').append($previewArea); - _.each(['link', 'primary', 'secondary'], type => { + ['link', 'primary', 'secondary'].forEach(type => { const $el = $(''); $el.appendTo($previewArea); this.__realMMColors[type] = { @@ -62,7 +62,7 @@ LinkDialog.include({ $preview.css('border-color', ''); $preview.css('background-color', ''); $preview.css('color', ''); - _.each(['link', 'primary', 'secondary'], type => { + ['link', 'primary', 'secondary'].forEach(type => { if ($preview.hasClass('btn-' + type) || type === 'link' && !$preview.hasClass('btn')) { $preview.css(this.__realMMColors[type]); } diff --git a/addons/point_of_sale/static/src/app/navbar/navbar.js b/addons/point_of_sale/static/src/app/navbar/navbar.js index 3df9be05c6a..1f5b33e5ff1 100644 --- a/addons/point_of_sale/static/src/app/navbar/navbar.js +++ b/addons/point_of_sale/static/src/app/navbar/navbar.js @@ -62,10 +62,7 @@ export class Navbar extends Component { this.env.pos.setLoadingOrderState(true); const message = await this.env.pos._syncAllOrdersFromServer(); if (message) { - this.notification.add( - _.str.sprintf(message), - 5000 - ); + this.notification.add(message, 5000); } } finally { this.env.pos.setLoadingOrderState(false); diff --git a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js index b7c548fc18e..9b71ac54f38 100644 --- a/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js +++ b/addons/point_of_sale/static/src/js/Popups/ClosePosPopup.js @@ -12,6 +12,7 @@ import { ConnectionLostError } from "@web/core/network/rpc_service"; import { identifyError } from "@point_of_sale/app/error_handlers/error_handlers"; import { _t } from "@web/core/l10n/translation"; import { usePos } from "@point_of_sale/app/pos_hook"; +import { sprintf } from "@web/core/utils/strings"; export class ClosePosPopup extends AbstractAwaitablePopup { static components = { SaleDetailsButton }; @@ -51,7 +52,7 @@ export class ClosePosPopup extends AbstractAwaitablePopup { } else { await this.popup.add(ConfirmPopup, { title: this.env._t("Payments Difference"), - body: _.str.sprintf( + body: sprintf( this.env._t( "The maximum difference allowed is %s.\n\ Please contact your manager to accept the closing difference." diff --git a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js index a1d50450225..dba87a9f7dc 100644 --- a/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/PartnerListScreen/PartnerListScreen.js @@ -8,6 +8,7 @@ import { PartnerLine } from "./PartnerLine"; import { PartnerDetailsEdit } from "./PartnerDetailsEdit"; import { usePos } from "@point_of_sale/app/pos_hook"; import { Component, onWillUnmount, useRef } from "@odoo/owl"; +import { sprintf } from "@web/core/utils/strings"; /** * Render this screen using `showTempScreen` to select partner. @@ -115,7 +116,7 @@ export class PartnerListScreen extends Component { const result = await this.searchPartner(); if (result.length > 0) { this.notification.add( - _.str.sprintf( + sprintf( this.env._t('%s customer(s) found for "%s".'), result.length, this.state.query @@ -124,14 +125,10 @@ export class PartnerListScreen extends Component { ); } else { this.notification.add( - _.str.sprintf( - this.env._t('No more customer found for "%s".'), - this.state.query - ), + sprintf(this.env._t('No more customer found for "%s".'), this.state.query), 3000 ); } - } _clearSearch() { this.searchWordInputRef.el.value = ""; @@ -188,7 +185,7 @@ export class PartnerListScreen extends Component { async getNewPartners() { let domain = []; const limit = 30; - if(this.state.query) { + if (this.state.query) { domain = [ "|", ["name", "ilike", this.state.query + "%"], @@ -199,7 +196,8 @@ export class PartnerListScreen extends Component { const result = await this.orm.silent.call( "pos.session", "get_pos_ui_res_partner_by_params", - [[odoo.pos_session_id], { domain, limit: limit, offset: this.state.currentOffset }]); + [[odoo.pos_session_id], { domain, limit: limit, offset: this.state.currentOffset }] + ); return result; } } diff --git a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js index f411ec0dc24..704a45dc584 100644 --- a/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/PaymentScreen/PaymentScreen.js @@ -16,6 +16,7 @@ import { PaymentScreenPaymentLines } from "./PaymentScreenPaymentLines"; import { PaymentScreenStatus } from "./PaymentScreenStatus"; import { usePos } from "@point_of_sale/app/pos_hook"; import { Component } from "@odoo/owl"; +import { sprintf } from "@web/core/utils/strings"; export class PaymentScreen extends Component { static template = "PaymentScreen"; @@ -334,7 +335,7 @@ export class PaymentScreen extends Component { const paymentMethod = splitPayments[0].payment_method; const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Customer Required"), - body: _.str.sprintf( + body: sprintf( this.env._t("Customer is required for %s payment method."), paymentMethod.name ), diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SaveButton.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SaveButton.js index 158039256c6..dc472d66420 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SaveButton.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ControlButtons/SaveButton.js @@ -6,7 +6,7 @@ import { useService } from "@web/core/utils/hooks"; export class SaveButton extends Component { static template = "point_of_sale.SaveButton"; - + setup() { super.setup(); this.notification = useService("pos_notification"); @@ -14,25 +14,15 @@ export class SaveButton extends Component { onClick() { const orderline = this.env.pos.get_order().get_selected_orderline(); if (!orderline) { - this.notification.add( - _.str.sprintf( - this.env._t('You cannot save an empty order') - ), - 3000 - ); + this.notification.add(this.env._t("You cannot save an empty order"), 3000); return; } this._selectEmptyOrder(); - this.notification.add( - _.str.sprintf( - this.env._t('Order saved for later') - ), - 3000 - ); + this.notification.add(this.env._t("Order saved for later"), 3000); } _selectEmptyOrder() { const orders = this.env.pos.get_order_list(); - let emptyOrders = orders.filter((order) => order.is_empty()); + const emptyOrders = orders.filter((order) => order.is_empty()); if (emptyOrders.length > 0) { this.env.pos.sendDraftToServer(); this.env.pos.set_order(emptyOrders[0]); @@ -40,7 +30,6 @@ export class SaveButton extends Component { this.env.pos.add_new_order(); } } - } ProductScreen.addControlButton({ diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js index 62bdfb4d2af..d13c0302bdd 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductScreen.js @@ -214,9 +214,9 @@ export class ProductScreen extends ControlButtonsMixin(Component) { } if (newQuantity >= selectedLine.saved_quantity) { if (newQuantity == 0) { - order.remove_orderline(selectedLine) + order.remove_orderline(selectedLine); } - selectedLine.set_quantity(newQuantity) + selectedLine.set_quantity(newQuantity); return; } const newLine = selectedLine.clone(); @@ -241,10 +241,7 @@ export class ProductScreen extends ControlButtonsMixin(Component) { this.pos.showScreen("TicketScreen", { ui: { filter: "SYNCED", searchDetails }, }); - this.notification.add( - _.str.sprintf(this.env._t("The order has been already paid.")), - 3000 - ); + this.notification.add(this.env._t("The order has been already paid."), 3000); this.env.pos.removeOrder(this.env.pos.get_order(), false); this.env.pos.add_new_order(); return; diff --git a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js index f7ced7d1555..df133f8a047 100644 --- a/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js +++ b/addons/point_of_sale/static/src/js/Screens/ProductScreen/ProductsWidget.js @@ -8,6 +8,7 @@ import { ConnectionLostError, ConnectionAbortedError } from "@web/core/network/r import { ProductItem } from "./ProductItem"; import { ProductsWidgetControlPanel } from "./ProductsWidgetControlPanel"; import { Component, useState } from "@odoo/owl"; +import { sprintf } from "@web/core/utils/strings"; export class ProductsWidget extends Component { static components = { ProductItem, ProductsWidgetControlPanel }; @@ -88,7 +89,7 @@ export class ProductsWidget extends Component { const result = await this.loadProductFromDB(); if (result.length > 0) { this.notification.add( - _.str.sprintf( + sprintf( this.env._t('%s product(s) found for "%s".'), result.length, this.env.pos.searchProductWord @@ -97,7 +98,7 @@ export class ProductsWidget extends Component { ); } else { this.notification.add( - _.str.sprintf( + sprintf( this.env._t('No more product found for "%s".'), this.env.pos.searchProductWord ), @@ -112,34 +113,47 @@ export class ProductsWidget extends Component { } } async loadProductFromDB() { - if(!this.env.pos.searchProductWord) + if (!this.env.pos.searchProductWord) { return; + } try { const limit = 30; - let ProductIds = await this.orm.call( + const ProductIds = await this.orm.call( "product.product", "search", - [['&',['available_in_pos', '=', true], '|','|', - ['name', 'ilike', this.env.pos.searchProductWord], - ['default_code', 'ilike', this.env.pos.searchProductWord], - ['barcode', 'ilike', this.env.pos.searchProductWord]]], + [ + [ + "&", + ["available_in_pos", "=", true], + "|", + "|", + ["name", "ilike", this.env.pos.searchProductWord], + ["default_code", "ilike", this.env.pos.searchProductWord], + ["barcode", "ilike", this.env.pos.searchProductWord], + ], + ], { offset: this.state.currentOffset, limit: limit, } ); - if(ProductIds.length) { + if (ProductIds.length) { await this.env.pos._addProducts(ProductIds, false); } this.updateProductList(); return ProductIds; } catch (error) { - const identifiedError = identifyError(error) - if (identifiedError instanceof ConnectionLostError || identifiedError instanceof ConnectionAbortedError) { + const identifiedError = identifyError(error); + if ( + identifiedError instanceof ConnectionLostError || + identifiedError instanceof ConnectionAbortedError + ) { return this.popup.add("OfflineErrorPopup", { title: this.env._t("Network Error"), - body: this.env._t("Product is not loaded. Tried loading the product from the server but there is a network error."), + body: this.env._t( + "Product is not loaded. Tried loading the product from the server but there is a network error." + ), }); } else { throw error; diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderlineDetails.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderlineDetails.js index d9f9bc1f399..ee1d486fdf7 100644 --- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderlineDetails.js +++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/OrderlineDetails.js @@ -1,6 +1,7 @@ /** @odoo-module */ import { Component } from "@odoo/owl"; +import { sprintf } from "@web/core/utils/strings"; import { format } from "web.field_utils"; import { round_precision as round_pr } from "web.utils"; @@ -67,9 +68,9 @@ export class OrderlineDetails extends Component { return this.env.pos.formatProductQty(toRefundDetail && toRefundDetail.qty); } getRefundingMessage() { - return _.str.sprintf(this.env._t("Refunding %s in "), this.getFormattedToRefundQty()); + return sprintf(this.env._t("Refunding %s in "), this.getFormattedToRefundQty()); } getToRefundMessage() { - return _.str.sprintf(this.env._t("To Refund: %s"), this.getFormattedToRefundQty()); + return sprintf(this.env._t("To Refund: %s"), this.getFormattedToRefundQty()); } } diff --git a/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js b/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js index 477c4962c96..fd8680aa7a8 100644 --- a/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js +++ b/addons/point_of_sale/static/src/js/Screens/TicketScreen/TicketScreen.js @@ -17,6 +17,7 @@ import { ReprintReceiptButton } from "./ControlButtons/ReprintReceiptButton"; import { SearchBar } from "../../Misc/SearchBar"; import { usePos } from "@point_of_sale/app/pos_hook"; import { onMounted, useState } from "@odoo/owl"; +import { sprintf } from "@web/core/utils/strings"; export class TicketScreen extends IndependentToOrderScreen { static template = "TicketScreen"; @@ -126,7 +127,7 @@ export class TicketScreen extends IndependentToOrderScreen { ) { const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Existing orderlines"), - body: _.str.sprintf( + body: sprintf( this.env._t( "%s has a total amount of %s, are you sure you want to delete this order?" ), @@ -207,7 +208,7 @@ export class TicketScreen extends IndependentToOrderScreen { this.numberBuffer.reset(); this.popup.add(ErrorPopup, { title: this.env._t("Maximum Exceeded"), - body: _.str.sprintf( + body: sprintf( this.env._t( "The requested quantity to be refunded is higher than the ordered quantity. %s is requested while only %s can be refunded." ), @@ -656,19 +657,28 @@ export class TicketScreen extends IndependentToOrderScreen { const limit = this._state.syncedOrders.nPerPage; const offset = (this._state.syncedOrders.currentPage - 1) * this._state.syncedOrders.nPerPage; - const { ordersInfo, totalCount } = await this.orm.call("pos.order", "search_paid_order_ids", [], { - config_id: this.env.pos.config.id, - domain, - limit, - offset, - }); - const idsNotInCache = ordersInfo.filter((orderInfo) => !(orderInfo[0] in this._state.syncedOrders.cache)); - const idsNotUpToDate = ordersInfo.filter((orderInfo) => format.datetime(moment(orderInfo[1]), {}, {}) > format.datetime(moment(this._state.syncedOrders.cacheDate), {}, { timezone: false })); + const { ordersInfo, totalCount } = await this.orm.call( + "pos.order", + "search_paid_order_ids", + [], + { + config_id: this.env.pos.config.id, + domain, + limit, + offset, + } + ); + const idsNotInCache = ordersInfo.filter( + (orderInfo) => !(orderInfo[0] in this._state.syncedOrders.cache) + ); + const idsNotUpToDate = ordersInfo.filter( + (orderInfo) => + format.datetime(moment(orderInfo[1]), {}, {}) > + format.datetime(moment(this._state.syncedOrders.cacheDate), {}, { timezone: false }) + ); const idsToLoad = idsNotInCache.concat(idsNotUpToDate).map((info) => info[0]); if (idsToLoad.length > 0) { - const fetchedOrders = await this.orm.call("pos.order", "export_for_ui", [ - idsToLoad, - ]); + const fetchedOrders = await this.orm.call("pos.order", "export_for_ui", [idsToLoad]); // Check for missing products and partners and load them in the PoS await this.env.pos._loadMissingProducts(fetchedOrders); await this.env.pos._loadMissingPartners(fetchedOrders); diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js index e557c489246..ef50ddd0468 100644 --- a/addons/point_of_sale/static/src/js/db.js +++ b/addons/point_of_sale/static/src/js/db.js @@ -493,9 +493,7 @@ export const PosDB = core.Class.extend({ }, remove_order: function (order_id) { var orders = this.load("orders", []); - orders = _.filter(orders, function (order) { - return order.id !== order_id; - }); + orders = orders.filter((order) => order.id !== order_id); this.save("orders", orders); }, remove_all_orders: function () { @@ -534,9 +532,7 @@ export const PosDB = core.Class.extend({ }, remove_unpaid_order: function (order) { var orders = this.load("unpaid_orders", []); - orders = _.filter(orders, function (o) { - return o.id !== order.uid; - }); + orders = orders.filter((o) => o.id !== order.uid); this.save("unpaid_orders", orders); }, remove_all_unpaid_orders: function () { @@ -591,9 +587,7 @@ export const PosDB = core.Class.extend({ set_ids_removed_from_server: function (ids) { var to_remove = this.load("unpaid_orders_to_remove", []); - to_remove = _.filter(to_remove, function (id) { - return !ids.includes(id); - }); + to_remove = to_remove.filter((id) => !ids.includes(id)); this.save("unpaid_orders_to_remove", to_remove); }, }); diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 6ec7aee5d8a..4dadd4ae251 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -12,12 +12,32 @@ import { ProductConfiguratorPopup } from "@point_of_sale/js/Popups/ProductConfig import { EditListPopup } from "@point_of_sale/js/Popups/EditListPopup"; import { markRaw, reactive } from "@odoo/owl"; import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup"; -import { escape } from "@web/core/utils/strings"; +import { escape, sprintf } from "@web/core/utils/strings"; import { Mutex } from "@web/core/utils/concurrency"; import { memoize } from "@web/core/utils/functions"; import { _t } from "@web/core/l10n/translation"; import { renderToString } from "@web/core/utils/render"; +/* Returns an array containing all elements of the given + * array corresponding to the rule function {agg} and without duplicates + * + * @template T + * @template F + * @param {T[]} array + * @param {F} function + * @returns {T[]} + */ +export function uniqueBy(array, agg) { + const map = new Map(); + for (const item of array) { + const key = agg(item); + if (!map.has(key)) { + map.set(key, item); + } + } + return [...map.values()]; +} + var round_di = utils.round_decimals; var round_pr = utils.round_precision; const Markup = utils.Markup; @@ -720,7 +740,7 @@ export class PosGlobalState extends PosModel { const pricelistsNames = pricelistsJson.map((pricelist) => { return pricelist.display_name; }); - message = _.str.sprintf( + message = sprintf( _t("%s fiscal position(s) added to the configuration."), pricelistsNames.join(", ") ); @@ -759,7 +779,7 @@ export class PosGlobalState extends PosModel { const fiscalPositionNames = fiscalPositionJson.map((fp) => { return fp.display_name; }); - message = _.str.sprintf( + message = sprintf( _t("%s fiscal position(s) added to the configuration."), fiscalPositionNames.join(", ") ); @@ -1115,7 +1135,7 @@ export class PosGlobalState extends PosModel { this.orders.add(orders); } - report.unpaid_skipped_sessions = _.keys(skipped_sessions); + report.unpaid_skipped_sessions = Object.keys(skipped_sessions); } return report; @@ -1394,7 +1414,7 @@ export class PosGlobalState extends PosModel { } } } - return _.uniq(mappedTaxes, (tax) => tax.id); + return uniqueBy(mappedTaxes, (tax) => tax.id); } /** @@ -1978,7 +1998,7 @@ export class Orderline extends PosModel { } else { this.pos.env.services.popup.add(ErrorPopup, { title: _t("Greater than allowed"), - body: _.str.sprintf( + body: sprintf( _t( "The requested quantity to be refunded is higher than the refundable quantity of %s." ), @@ -2377,7 +2397,7 @@ export class Orderline extends PosModel { var product = this.get_product(); var taxes_ids = this.tax_ids || product.taxes_id; - taxes_ids = _.filter(taxes_ids, (t) => t in this.pos.taxes_by_id); + taxes_ids = taxes_ids.filter((t) => t in this.pos.taxes_by_id); var taxdetail = {}; var product_taxes = this.pos.get_taxes_after_fp(taxes_ids, this.order.fiscal_position); @@ -2700,7 +2720,7 @@ export class Order extends PosModel { this.access_token = uuidv4(); // unique uuid used to identify the authenticity of the request from the QR code. this.ticketCode = this._generateTicketCode(); // 5-digits alphanum code shown on the receipt this.uid = this.generate_unique_id(); - this.name = _.str.sprintf(_t("Order %s"), this.uid); + this.name = sprintf(_t("Order %s"), this.uid); this.validation_date = undefined; this.fiscal_position = this.pos.fiscal_positions.find(function (fp) { return fp.id === self.pos.config.default_fiscal_position_id[0]; @@ -2737,7 +2757,7 @@ export class Order extends PosModel { } this.session_id = this.pos.pos_session.id; this.uid = json.uid; - this.name = _.str.sprintf(_t("Order %s"), this.uid); + this.name = sprintf(_t("Order %s"), this.uid); this.validation_date = json.creation_date; this.server_id = json.server_id ? json.server_id : false; this.user_id = json.user_id; @@ -3139,9 +3159,7 @@ export class Order extends PosModel { var self = this; this.pricelist = pricelist; - var lines_to_recompute = _.filter(this.get_orderlines(), function (line) { - return !line.price_manually_set; - }); + var lines_to_recompute = this.get_orderlines().filter((line) => !line.price_manually_set); _.each(lines_to_recompute, function (line) { line.set_unit_price( line.product.get_price(self.pricelist, line.get_quantity(), line.get_price_extra()) diff --git a/addons/point_of_sale/static/tests/tours/pricelist_tour.js b/addons/point_of_sale/static/tests/tours/pricelist_tour.js index e9edc344f23..9c57335b005 100644 --- a/addons/point_of_sale/static/tests/tours/pricelist_tour.js +++ b/addons/point_of_sale/static/tests/tours/pricelist_tour.js @@ -13,7 +13,7 @@ function assert(condition, message) { function assertProductPrice(product, pricelist_name, quantity, expected_price) { return function () { - var pricelist = _.findWhere(posmodel.pricelists, { name: pricelist_name }); + var pricelist = posmodel.pricelists.find((pricelist) => pricelist.name === pricelist_name); var frontend_price = product.get_price(pricelist, quantity); frontend_price = round_di(frontend_price, posmodel.dp["Product Price"]); diff --git a/addons/portal/static/src/js/portal_chatter.js b/addons/portal/static/src/js/portal_chatter.js index 26ae58bf524..2d20150a626 100644 --- a/addons/portal/static/src/js/portal_chatter.js +++ b/addons/portal/static/src/js/portal_chatter.js @@ -6,6 +6,8 @@ import publicWidget from "web.public.widget"; import time from "web.time"; import portalComposer from "portal.composer"; import {Markup} from "web.utils"; +import { range } from "@web/core/utils/numbers"; +import { sprintf } from "@web/core/utils/strings"; var qweb = core.qweb; var _t = core._t; @@ -101,8 +103,8 @@ var PortalChatter = publicWidget.Widget.extend({ */ preprocessMessages(messages) { _.each(messages, function (m) { - m['author_avatar_url'] = _.str.sprintf('/web/image/%s/%s/author_avatar/50x50', 'mail.message', m.id); - m['published_date_str'] = _.str.sprintf(_t('Published on %s'), moment(time.str_to_datetime(m.date)).format('MMMM Do YYYY, h:mm:ss a')); + m['author_avatar_url'] = sprintf('/web/image/%s/%s/author_avatar/50x50', 'mail.message', m.id); + m['published_date_str'] = sprintf(_t('Published on %s'), moment(time.str_to_datetime(m.date)).format('MMMM Do YYYY, h:mm:ss a')); m['body'] = Markup(m.body); }); return messages; @@ -245,9 +247,7 @@ var PortalChatter = publicWidget.Widget.extend({ } var pages = []; - _.each(_.range(pmin, pmax + 1), function (index) { - pages.push(index); - }); + range(pmin, pmax + 1).forEach(index => pages.push(index)); return { "page_count": pageCount, diff --git a/addons/portal/static/src/js/portal_composer.js b/addons/portal/static/src/js/portal_composer.js index 8908193cc4f..6e91d5e2084 100644 --- a/addons/portal/static/src/js/portal_composer.js +++ b/addons/portal/static/src/js/portal_composer.js @@ -1,5 +1,6 @@ /** @odoo-module alias=portal.composer **/ +import { sprintf } from "@web/core/utils/strings"; import ajax from "web.ajax"; import core from "web.core"; import publicWidget from "web.public.widget"; @@ -91,7 +92,7 @@ var PortalComposer = publicWidget.Widget.extend({ 'access_token': accessToken, }, }).then(function () { - self.attachments = _.reject(self.attachments, {'id': attachmentId}); + self.attachments = self.attachments.filter(attachment => attachment.id !== attachmentId); self._updateAttachments(); self.$sendButton.prop('disabled', false); }); @@ -124,7 +125,7 @@ var PortalComposer = publicWidget.Widget.extend({ resolve(); }).guardedCatch(function (error) { self.displayNotification({ - message: _.str.sprintf(_t("Could not save file %s"), + message: sprintf(_t("Could not save file %s"), _.escape(file.name)), type: 'warning', sticky: true, diff --git a/addons/portal/static/src/js/portal_sidebar.js b/addons/portal/static/src/js/portal_sidebar.js index bc8ceda33cd..f9f90ad7669 100644 --- a/addons/portal/static/src/js/portal_sidebar.js +++ b/addons/portal/static/src/js/portal_sidebar.js @@ -4,6 +4,7 @@ import core from "web.core"; import publicWidget from "web.public.widget"; import time from "web.time"; import session from "web.session"; +import { sprintf } from "@web/core/utils/strings"; var _t = core._t; @@ -40,9 +41,9 @@ var PortalSidebar = publicWidget.Widget.extend({ } else if (diff > 0) { // Workaround: force uniqueness of these two translations. We use %1d because the string // with %d is already used in mail and mail's translations are not sent to the frontend. - displayStr = _.str.sprintf(_t('Due in %1d days'), Math.abs(diff)); + displayStr = sprintf(_t('Due in %s days'), Math.abs(diff).toFixed(1)); } else { - displayStr = _.str.sprintf(_t('%1d days overdue'), Math.abs(diff)); + displayStr = sprintf(_t('%s days overdue'), Math.abs(diff).toFixed(1)); } $(el).text(displayStr); }); diff --git a/addons/portal_rating/static/src/js/portal_chatter.js b/addons/portal_rating/static/src/js/portal_chatter.js index 56ba6dc8975..e2fb17b535c 100644 --- a/addons/portal_rating/static/src/js/portal_chatter.js +++ b/addons/portal_rating/static/src/js/portal_chatter.js @@ -136,7 +136,7 @@ PortalChatter.include({ 'avg': Math.round(result['rating_stats']['avg'] * 100) / 100, 'percent': [], }; - _.each(_.sortBy(_.keys(result['rating_stats']['percent'])).reverse(), function (rating) { + _.each(_.sortBy(Object.keys(result['rating_stats']['percent'])).reverse(), function (rating) { ratingData['percent'].push({ 'num': self.roundToHalf(rating), 'percent': utils.round_precision(result['rating_stats']['percent'][rating], 0.01), @@ -172,7 +172,7 @@ PortalChatter.include({ return { mes_index: messageIndex, publisher_id: this.options.partner_id, - publisher_avatar: _.str.sprintf('/web/image/res.partner/%s/avatar_128/50x50', this.options.partner_id), + publisher_avatar: `/web/image/res.partner/${this.options.partner_id}/avatar_128/50x50`, publisher_name: _t("Write your comment"), publisher_datetime: '', publisher_comment: '', @@ -197,7 +197,7 @@ PortalChatter.include({ if (rawRating.publisher_id && rawRating.publisher_id.length >= 2) { ratingData.publisher_id = rawRating.publisher_id[0]; ratingData.publisher_name = rawRating.publisher_id[1]; - ratingData.publisher_avatar = _.str.sprintf('/web/image/res.partner/%s/avatar_128/50x50', ratingData.publisher_id); + ratingData.publisher_avatar = `/web/image/res.partner/${ratingData.publisher_id}/avatar_128/50x50`; } var commentData = Object.assign(this._newPublisherCommentData(messageIndex), ratingData); return commentData; diff --git a/addons/pos_adyen/static/src/js/payment_adyen.js b/addons/pos_adyen/static/src/js/payment_adyen.js index 8eb651511b7..e29b75f3d22 100644 --- a/addons/pos_adyen/static/src/js/payment_adyen.js +++ b/addons/pos_adyen/static/src/js/payment_adyen.js @@ -4,6 +4,7 @@ import core from "web.core"; import rpc from "web.rpc"; import { PaymentInterface } from "@point_of_sale/js/payment"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; +import { sprintf } from "@web/core/utils/strings"; var _t = core._t; @@ -78,7 +79,7 @@ export const PaymentAdyen = PaymentInterface.extend({ _adyen_get_sale_id: function () { var config = this.pos.config; - return _.str.sprintf("%s (ID: %s)", config.display_name, config.id); + return sprintf("%s (ID: %s)", config.display_name, config.id); }, _adyen_common_message_header: function () { @@ -192,9 +193,9 @@ export const PaymentAdyen = PaymentInterface.extend({ return output_text.reduce(function (acc, entry) { var params = new URLSearchParams(entry.Text); if (params.get("name") && !params.get("value")) { - return acc + _.str.sprintf("\n%s", params.get("name")); + return acc + sprintf("\n%s", params.get("name")); } else if (params.get("name") && params.get("value")) { - return acc + _.str.sprintf("\n%s: %s", params.get("name"), params.get("value")); + return acc + sprintf("\n%s: %s", params.get("name"), params.get("value")); } return acc; @@ -287,7 +288,7 @@ export const PaymentAdyen = PaymentInterface.extend({ resolve(true); } else { var message = additional_response.get("message"); - self._show_error(_.str.sprintf(_t("Message from Adyen: %s"), message)); + self._show_error(sprintf(_t("Message from Adyen: %s"), message)); // this means the transaction was cancelled by pressing the cancel button on the device if (message.startsWith("108 ")) { @@ -327,7 +328,7 @@ export const PaymentAdyen = PaymentInterface.extend({ } this._show_error( - _.str.sprintf(_t("An unexpected error occurred. Message from Adyen: %s"), msg) + sprintf(_t("An unexpected error occurred. Message from Adyen: %s"), msg) ); if (line) { line.set_payment_status("force_done"); diff --git a/addons/pos_discount/static/src/js/DiscountButton.js b/addons/pos_discount/static/src/js/DiscountButton.js index b452dc2ac2f..691850e6e1a 100644 --- a/addons/pos_discount/static/src/js/DiscountButton.js +++ b/addons/pos_discount/static/src/js/DiscountButton.js @@ -5,6 +5,7 @@ import { useService } from "@web/core/utils/hooks"; import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; import { Component } from "@odoo/owl"; +import { sprintf } from "@web/core/utils/strings"; export class DiscountButton extends Component { static template = "DiscountButton"; @@ -68,7 +69,7 @@ export class DiscountButton extends Component { description: `${pc}%, ` + (tax_ids_array.length - ? _.str.sprintf( + ? sprintf( this.env._t("Tax: %s"), tax_ids_array .map((taxId) => this.env.pos.taxes_by_id[taxId].amount + "%") diff --git a/addons/pos_epson_printer/static/src/js/printers.js b/addons/pos_epson_printer/static/src/js/printers.js index c371adc411f..2f482d76f5d 100644 --- a/addons/pos_epson_printer/static/src/js/printers.js +++ b/addons/pos_epson_printer/static/src/js/printers.js @@ -2,6 +2,7 @@ import core from "web.core"; import { PrinterMixin, PrintResult, PrintResultGenerator } from "@point_of_sale/js/printers"; +import { sprintf } from "@web/core/utils/strings"; var QWeb = core.qweb; var _t = core._t; @@ -19,15 +20,15 @@ class EpsonPrintResultGenerator extends PrintResultGenerator { title: _t("Connection to the printer failed"), body: _t( "Please check if the printer is still connected. \n" + - "Some browsers don't allow HTTP calls from websites to devices in the network (for security reasons). " + - "If it is the case, you will need to follow Odoo's documentation for " + - "'Self-signed certificate for ePOS printers' and 'Secure connection (HTTPS)' to solve the issue" + "Some browsers don't allow HTTP calls from websites to devices in the network (for security reasons). " + + "If it is the case, you will need to follow Odoo's documentation for " + + "'Self-signed certificate for ePOS printers' and 'Secure connection (HTTPS)' to solve the issue" ), }, }); if (window.location.protocol === "https:") { - printRes.message.body += _.str.sprintf( + printRes.message.body += sprintf( _t( "If you are on a secure server (HTTPS) please make sure you manually accepted the certificate by accessing %s" ), @@ -39,22 +40,32 @@ class EpsonPrintResultGenerator extends PrintResultGenerator { } IoTResultError(printerErrorCode) { - let message = _t("The printer was successfully reached, but it wasn't able to print.") + '\n'; + let message = + _t("The printer was successfully reached, but it wasn't able to print.") + "\n"; if (printerErrorCode) { - message += '\n' + _t("The following error code was given by the printer:") + '\n' + printerErrorCode; + message += + "\n" + + _t("The following error code was given by the printer:") + + "\n" + + printerErrorCode; const extra_messages = { - "DeviceNotFound": - _t("Check on the printer configuration for the 'Device ID' setting. " + - "It should be set to: ") + "\nlocal_printer", - "EPTR_REC_EMPTY": - _t("No paper was detected by the printer"), + DeviceNotFound: + _t( + "Check on the printer configuration for the 'Device ID' setting. " + + "It should be set to: " + ) + "\nlocal_printer", + EPTR_REC_EMPTY: _t("No paper was detected by the printer"), }; if (printerErrorCode in extra_messages) { - message += '\n' + extra_messages[printerErrorCode]; + message += "\n" + extra_messages[printerErrorCode]; } - message += '\n' + _t("To find more details on the error reason, please search online for:") + '\n' + - " Epson Server Direct Print " + printerErrorCode; + message += + "\n" + + _t("To find more details on the error reason, please search online for:") + + "\n" + + " Epson Server Direct Print " + + printerErrorCode; } else { message += _t("Please check if the printer has enough paper and is ready to print."); } @@ -184,6 +195,9 @@ export const EpsonPrinter = core.Class.extend(PrinterMixin, { data: img, }); const response = $(res).find("response"); - return {"result": response.attr("success") === "true", "printerErrorCode": response.attr("code")}; + return { + result: response.attr("success") === "true", + printerErrorCode: response.attr("code"), + }; }, }); diff --git a/addons/pos_loyalty/static/src/js/PartnerLine.js b/addons/pos_loyalty/static/src/js/PartnerLine.js index 963d9136164..0043ec66d6e 100644 --- a/addons/pos_loyalty/static/src/js/PartnerLine.js +++ b/addons/pos_loyalty/static/src/js/PartnerLine.js @@ -2,6 +2,7 @@ import { PartnerLine } from "@point_of_sale/js/Screens/PartnerListScreen/PartnerLine"; import { patch } from "@web/core/utils/patch"; +import { sprintf } from "@web/core/utils/strings"; patch(PartnerLine.prototype, "pos_loyalty.PartnerLine", { _getLoyaltyPointsRepr(loyaltyCard) { @@ -13,6 +14,6 @@ patch(PartnerLine.prototype, "pos_loyalty.PartnerLine", { if (program.portal_visible) { return `${balanceRepr} ${program.portal_point_name}`; } - return _.str.sprintf(this.env._t("%s Points"), balanceRepr); + return sprintf(this.env._t("%s Points"), balanceRepr); }, }); diff --git a/addons/pos_loyalty/static/src/js/ProductScreen.js b/addons/pos_loyalty/static/src/js/ProductScreen.js index 10a3c63fde7..8ba36e56a31 100644 --- a/addons/pos_loyalty/static/src/js/ProductScreen.js +++ b/addons/pos_loyalty/static/src/js/ProductScreen.js @@ -5,6 +5,7 @@ import { useBarcodeReader } from "@point_of_sale/app/barcode_reader_hook"; import { patch } from "@web/core/utils/patch"; import { ConfirmPopup } from "@point_of_sale/js/Popups/ConfirmPopup"; import { useService } from "@web/core/utils/hooks"; +import { sprintf } from "@web/core/utils/strings"; patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", { setup() { @@ -43,7 +44,7 @@ patch(ProductScreen.prototype, "pos_loyalty.ProductScreen", { const reward = this.env.pos.reward_by_id[selectedLine.reward_id]; const { confirmed } = await this.popup.add(ConfirmPopup, { title: this.env._t("Deactivating reward"), - body: _.str.sprintf( + body: sprintf( this.env._t( "Are you sure you want to remove %s from this order?\n You will still be able to claim it through the reward button." ), diff --git a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js index b67b4fe59fe..e93ef0de357 100644 --- a/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js +++ b/addons/pos_restaurant/static/src/js/Screens/ProductScreen/ControlButtons/TableGuestsButton.js @@ -5,6 +5,7 @@ import { useService } from "@web/core/utils/hooks"; import { NumberPopup } from "@point_of_sale/js/Popups/NumberPopup"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; import { Component } from "@odoo/owl"; +import { sprintf } from "@web/core/utils/strings"; export class TableGuestsButton extends Component { static template = "TableGuestsButton"; @@ -34,7 +35,7 @@ export class TableGuestsButton extends Component { if (guestCount > max_capacity) { await this.popup.add(ErrorPopup, { title: this.env._t("Blocked action"), - body: _.str.sprintf( + body: sprintf( this.env._t("You cannot put a number that exceeds %s "), max_capacity ), diff --git a/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js b/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js index ed2ddaec298..587311fe6ab 100644 --- a/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js +++ b/addons/pos_sale/static/src/js/OrderManagementScreen/SaleOrderManagementScreen.js @@ -98,7 +98,7 @@ export class SaleOrderManagementScreen extends ControlButtonsMixin(IndependentTo await this.env.pos._loadPartners([sale_order.partner_id[0]]); } catch { const title = this.env._t("Customer loading error"); - const body = _.str.sprintf( + const body = sprintf( this.env._t("There was a problem in loading the %s customer."), sale_order.partner_id[1] ); diff --git a/addons/pos_stripe/static/src/js/payment_stripe.js b/addons/pos_stripe/static/src/js/payment_stripe.js index 30e585024e7..267c7c84998 100644 --- a/addons/pos_stripe/static/src/js/payment_stripe.js +++ b/addons/pos_stripe/static/src/js/payment_stripe.js @@ -5,6 +5,7 @@ import core from "web.core"; import rpc from "web.rpc"; import { PaymentInterface } from "@point_of_sale/js/payment"; import { ErrorPopup } from "@point_of_sale/js/Popups/ErrorPopup"; +import { sprintf } from "@web/core/utils/strings"; const _t = core._t; @@ -48,7 +49,7 @@ export const PaymentStripe = PaymentInterface.extend({ discoverReaders: async function () { const discoverResult = await this.terminal.discoverReaders({}); if (discoverResult.error) { - this._showError(_.str.sprintf(_t("Failed to discover: %s"), discoverResult.error)); + this._showError(sprintf(_t("Failed to discover: %s"), discoverResult.error)); } else if (discoverResult.discoveredReaders.length === 0) { this._showError(_t("No available Stripe readers.")); } else { @@ -100,7 +101,7 @@ export const PaymentStripe = PaymentInterface.extend({ } } this._showError( - _.str.sprintf( + sprintf( this.env._t("Stripe readers %s not listed in your account"), this.payment_method.stripe_serial_number ) diff --git a/addons/sale/static/src/js/variant_mixin.js b/addons/sale/static/src/js/variant_mixin.js index 5bcdbeee58f..c9e9249441a 100644 --- a/addons/sale/static/src/js/variant_mixin.js +++ b/addons/sale/static/src/js/variant_mixin.js @@ -4,6 +4,7 @@ import concurrency from "web.concurrency"; import core from "web.core"; import utils from "web.utils"; import ajax from "web.ajax"; +import { sprintf } from "@web/core/utils/strings"; var _t = core._t; var VariantMixin = { @@ -500,7 +501,7 @@ var VariantMixin = { } excludedByData.push(excludedByName); - $target.attr('title', _.str.sprintf(_t('Not available with %s'), excludedByData.join(', '))); + $target.attr('title', sprintf(_t('Not available with %s'), excludedByData.join(', '))); $target.data('excluded-by', JSON.stringify(excludedByData)); } }, @@ -600,7 +601,7 @@ var VariantMixin = { if ($('.decimal_precision').length) { precision = parseInt($('.decimal_precision').last().data('precision')); } - var formatted = _.str.sprintf('%.' + precision + 'f', price).split('.'); + var formatted = price.toFixed(precision).split("."); formatted[0] = utils.insert_thousand_seps(formatted[0]); return formatted.join(l10n.decimal_point); }, diff --git a/addons/spreadsheet/static/src/list/list_functions.js b/addons/spreadsheet/static/src/list/list_functions.js index 32dc8066981..f6c41ce8bd0 100644 --- a/addons/spreadsheet/static/src/list/list_functions.js +++ b/addons/spreadsheet/static/src/list/list_functions.js @@ -2,6 +2,7 @@ import { _t } from "web.core"; import * as spreadsheet from "@odoo/o-spreadsheet"; +import { sprintf } from "@web/core/utils/strings"; const { arg, toString, toNumber } = spreadsheet.helpers; const { functionRegistry } = spreadsheet.registries; @@ -12,7 +13,7 @@ const { functionRegistry } = spreadsheet.registries; function assertListsExists(listId, getters) { if (!getters.isExistingList(listId)) { - throw new Error(_.str.sprintf(_t('There is no list with id "%s"'), listId)); + throw new Error(sprintf(_t('There is no list with id "%s"'), listId)); } } diff --git a/addons/survey/static/src/js/survey_form.js b/addons/survey/static/src/js/survey_form.js index 6a2b6b12383..9050e3d9a3e 100644 --- a/addons/survey/static/src/js/survey_form.js +++ b/addons/survey/static/src/js/survey_form.js @@ -483,7 +483,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa } var submitPromise = self._rpc({ - route: _.str.sprintf('%s/%s/%s', route, self.options.surveyToken, self.options.answerToken), + route: `${route}/${self.options.surveyToken}/${self.options.answerToken}`, params: params, }); this._nextScreen(submitPromise, options); @@ -736,7 +736,7 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa break; } }); - if (_.keys(errors).length > 0) { + if (Object.keys(errors).length > 0) { this._showErrors(errors); return false; } @@ -1170,8 +1170,8 @@ publicWidget.registry.SurveyFormWidget = publicWidget.Widget.extend(SurveyPreloa _showErrors: function (errors) { var self = this; - var errorKeys = _.keys(errors); - _.each(errorKeys, function (key) { + var errorKeys = Object.keys(errors || {}); + errorKeys.forEach(key => { self.$("#" + key + '>.o_survey_question_error').append($('

', {text: errors[key]})).addClass("slide_in"); if (errorKeys[0] === key) { self._scrollToError(self.$('.js_question-wrapper#' + key)); diff --git a/addons/survey/static/src/js/survey_session_leaderboard.js b/addons/survey/static/src/js/survey_session_leaderboard.js index 67208d1ce32..9ddaf849132 100644 --- a/addons/survey/static/src/js/survey_session_leaderboard.js +++ b/addons/survey/static/src/js/survey_session_leaderboard.js @@ -44,7 +44,7 @@ publicWidget.registry.SurveySessionLeaderboard = publicWidget.Widget.extend({ } var leaderboardPromise = this._rpc({ - route: _.str.sprintf('/survey/session/leaderboard/%s', this.surveyAccessToken) + route: `/survey/session/leaderboard/${this.surveyAccessToken}`, }); Promise.all([fadeOutPromise, leaderboardPromise]).then(function (results) { diff --git a/addons/survey/static/src/js/survey_session_manage.js b/addons/survey/static/src/js/survey_session_manage.js index 61af1b7e32b..e248c525667 100644 --- a/addons/survey/static/src/js/survey_session_manage.js +++ b/addons/survey/static/src/js/survey_session_manage.js @@ -250,10 +250,7 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend(SurveyPre args: [[this.surveyId]], }).then(function () { if ($(ev.currentTarget).data('showResults')) { - document.location = _.str.sprintf( - '/survey/results/%s', - self.surveyId - ); + document.location = `/survey/results/${encodeURIComponent(self.surveyId)}`; } else { window.history.back(); } @@ -359,7 +356,7 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend(SurveyPre } var nextQuestionPromise = this._rpc({ - route: _.str.sprintf('/survey/session/next_question/%s', self.surveyAccessToken), + route: `/survey/session/next_question/${self.surveyAccessToken}`, params: { 'go_back': goBack, } @@ -472,7 +469,7 @@ publicWidget.registry.SurveySessionManage = publicWidget.Widget.extend(SurveyPre var self = this; return this._rpc({ - route: _.str.sprintf('/survey/session/results/%s', self.surveyAccessToken) + route: `/survey/session/results/${self.surveyAccessToken}`, }).then(function (questionResults) { if (questionResults) { self.attendeesCount = questionResults.attendees_count; diff --git a/addons/web/static/src/core/debug/profiling/profiling_qweb.js b/addons/web/static/src/core/debug/profiling/profiling_qweb.js index 959f6639500..21249de6e9c 100644 --- a/addons/web/static/src/core/debug/profiling/profiling_qweb.js +++ b/addons/web/static/src/core/debug/profiling/profiling_qweb.js @@ -116,7 +116,7 @@ export class ProfilingQwebView extends Component { * @returns {string} */ _formatDelay(delay) { - return delay ? _.str.sprintf("%.1f", Math.ceil(delay * 10) / 10) : "."; + return delay ? (Math.ceil(delay * 10) / 10).toFixed(1) : "."; } /** diff --git a/addons/web/static/src/core/utils/numbers.js b/addons/web/static/src/core/utils/numbers.js index 89432e3ce81..b5f4f2cb4c5 100644 --- a/addons/web/static/src/core/utils/numbers.js +++ b/addons/web/static/src/core/utils/numbers.js @@ -32,3 +32,20 @@ export function computeVariation(value, comparisonValue) { export function clamp(num, min, max) { return Math.max(Math.min(num, max), min); } + +/** + * A function to create flexibly-numbered lists of integers, handy for each and map loops. + * step defaults to 1. + * Returns a list of integers from start (inclusive) to stop (exclusive), incremented (or decremented) by step. + * @param {number} min default 0 + * @param {number} max + * @param {number} step default 1 + * @returns {number[]} + */ +export function range(start, stop, step = 1) { + const array = []; + for (let i = start; i < stop; i += step) { + array.push(i); + } + return array; +} diff --git a/addons/web/static/src/legacy/frontend/keyboard_navigation_mixin.js b/addons/web/static/src/legacy/frontend/keyboard_navigation_mixin.js index fc1e9aa8467..12b1ae0fa12 100644 --- a/addons/web/static/src/legacy/frontend/keyboard_navigation_mixin.js +++ b/addons/web/static/src/legacy/frontend/keyboard_navigation_mixin.js @@ -1,5 +1,5 @@ /** @odoo-module alias=web.KeyboardNavigationMixin **/ - import BrowserDetection from "web.BrowserDetection"; +import BrowserDetection from "web.BrowserDetection"; import core from "web.core"; /** @@ -77,7 +77,7 @@ } var accesskeyElements = $(document).find('[accesskey]').filter(':visible'); _.each(accesskeyElements, function (elem) { - var overlay = $(_.str.sprintf("

%s
", $(elem).attr('accesskey').toUpperCase())); + var overlay = $(`
${$(elem).attr('accesskey').toUpperCase()}
`); var $overlayParent; if (elem.tagName.toUpperCase() === "INPUT") { diff --git a/addons/web/static/src/legacy/js/control_panel/control_panel.js b/addons/web/static/src/legacy/js/control_panel/control_panel.js index 7832a31ad70..291571b0eea 100644 --- a/addons/web/static/src/legacy/js/control_panel/control_panel.js +++ b/addons/web/static/src/legacy/js/control_panel/control_panel.js @@ -10,6 +10,7 @@ import SearchBar from "web.SearchBar"; import { useModel } from "web.Model"; import { LegacyComponent } from "@web/legacy/legacy_component"; +import { sprintf } from "@web/core/utils/strings"; const { onMounted, @@ -132,7 +133,7 @@ this.fields = this._formatFields(toRaw(this.props.fields || {})); - this.sprintf = _.str.sprintf; + this.sprintf = sprintf; onWillDestroy(() => { const content = this.props.cp_content; diff --git a/addons/web/static/src/legacy/js/core/dialog.js b/addons/web/static/src/legacy/js/core/dialog.js index 9069211348d..ec803e51a25 100644 --- a/addons/web/static/src/legacy/js/core/dialog.js +++ b/addons/web/static/src/legacy/js/core/dialog.js @@ -312,7 +312,7 @@ var Dialog = Widget.extend({ _setButtonsTo($target, buttons) { var self = this; $target.empty(); - _.each(buttons, function (buttonData) { + buttons.forEach(buttonData => { var $button = dom.renderButton({ attrs: { class: buttonData.classes || (buttons.length > 1 ? 'btn-secondary' : 'btn-primary'), diff --git a/addons/web/static/src/legacy/js/core/domain.js b/addons/web/static/src/legacy/js/core/domain.js index 52c6f3a2300..4de962c4a54 100644 --- a/addons/web/static/src/legacy/js/core/domain.js +++ b/addons/web/static/src/legacy/js/core/domain.js @@ -1,5 +1,6 @@ /** @odoo-module alias=web.Domain **/ +import { intersection } from "@web/core/utils/arrays"; import collections from "web.collections"; import pyUtils from "web.py_utils"; var py = window.py; // look py.js @@ -34,7 +35,7 @@ var Domain = collections.Tree.extend({ */ init: function (domain, evalContext) { this._super.apply(this, arguments); - if (_.isArray(domain) || _.isString(domain)) { + if (Array.isArray(domain) || _.isString(domain)) { this._parse(this.normalizeArray(_.clone(this.stringToArray(domain, evalContext)))); } else { this._data = !!domain; @@ -57,7 +58,7 @@ var Domain = collections.Tree.extend({ if (this._data === true || this._data === false) { // The domain is a always-true or a always-false domain return this._data; - } else if (_.isArray(this._data)) { + } else if (Array.isArray(this._data)) { // The domain is a [name, operator, value] entity // First check if we have the field value in the field values set // and if the first part of the domain contains 'parent.field' @@ -78,10 +79,7 @@ var Domain = collections.Tree.extend({ fieldName in values.parent; } if (!(this._data[0] in values) && !(isParentField)) { - throw new Error(_.str.sprintf( - "Unknown field %s in domain", - this._data[0] - )); + throw new Error(`Unknown field ${this._data[0]} in domain`); } fieldValue = isParentField ? values.parent[fieldName] : values[fieldName]; } @@ -102,14 +100,14 @@ var Domain = collections.Tree.extend({ case ">=": return (fieldValue >= this._data[2]); case "in": - return _.intersection( - _.isArray(this._data[2]) ? this._data[2] : [this._data[2]], - _.isArray(fieldValue) ? fieldValue : [fieldValue], + return intersection( + Array.isArray(this._data[2]) ? this._data[2] : [this._data[2]], + Array.isArray(fieldValue) ? fieldValue : [fieldValue], ).length !== 0; case "not in": - return _.intersection( - _.isArray(this._data[2]) ? this._data[2] : [this._data[2]], - _.isArray(fieldValue) ? fieldValue : [fieldValue], + return intersection( + Array.isArray(this._data[2]) ? this._data[2] : [this._data[2]], + Array.isArray(fieldValue) ? fieldValue : [fieldValue], ).length === 0; case "like": if (fieldValue === false) { @@ -132,10 +130,7 @@ var Domain = collections.Tree.extend({ } return new RegExp(this._data[2].replace(/%/g, '.*'), 'i').test(fieldValue); default: - throw new Error(_.str.sprintf( - "Domain %s uses an unsupported operator", - this._data - )); + throw new Error(`Domain ${this._data} uses an unsupported operator`); } } else { // The domain is a set of [name, operator, value] entitie(s) switch (this._data) { @@ -289,7 +284,7 @@ var Domain = collections.Tree.extend({ normalizeArray: function (domain) { if (domain.length === 0) { return domain; } var expected = 1; - _.each(domain, function (item) { + domain.forEach(item => { if (item === "&" || item === "|") { expected++; } else if (item !== "!") { @@ -299,10 +294,7 @@ var Domain = collections.Tree.extend({ if (expected < 0) { domain.unshift.apply(domain, _.times(Math.abs(expected), _.constant("&"))); } else if (expected > 0) { - throw new Error(_.str.sprintf( - "invalid domain %s (missing %d segment(s))", - JSON.stringify(domain), expected - )); + throw new Error(`invalid domain ${JSON.stringify(domain)} (missing ${expected.toFixed(0)} segment(s))`); } return domain; }, diff --git a/addons/web/static/src/legacy/js/core/mixins.js b/addons/web/static/src/legacy/js/core/mixins.js index df1f1e07af2..4644e1a9ec7 100644 --- a/addons/web/static/src/legacy/js/core/mixins.js +++ b/addons/web/static/src/legacy/js/core/mixins.js @@ -305,7 +305,7 @@ var EventDispatcherMixin = Object.assign({}, ParentedMixin, { _.each(events, function (eventName) { self.__edispatcherEvents.off(eventName, func, dest); if (dest && dest.__eventDispatcherMixin) { - dest.__edispatcherRegisteredEvents = _.filter(dest.__edispatcherRegisteredEvents, function (el) { + dest.__edispatcherRegisteredEvents = dest.__edispatcherRegisteredEvents.filter(el => { return !(el.name === eventName && el.func === func && el.source === self); }); } @@ -386,7 +386,7 @@ var PropertiesMixin = Object.assign({}, EventDispatcherMixin, { // remove this, or move it elsewhere. Also, learn OO programming. if (key === 'value' && self.field && self.field.type === 'float' && tmp && val){ var digits = self.field.digits; - if (_.isArray(digits)) { + if (Array.isArray(digits)) { if (utils.float_is_zero(tmp - val, digits[1])) { return; } diff --git a/addons/web/static/src/legacy/js/core/ram_storage.js b/addons/web/static/src/legacy/js/core/ram_storage.js index eeed9e29734..655d25fa975 100644 --- a/addons/web/static/src/legacy/js/core/ram_storage.js +++ b/addons/web/static/src/legacy/js/core/ram_storage.js @@ -46,7 +46,7 @@ var RamStorage = Class.extend(mixins.EventDispatcherMixin, { * @return {string} */ key: function (index) { - return _.keys(this.storage)[index]; + return Object.keys(this.storage)[index]; }, /** * Removes the given key from the storage diff --git a/addons/web/static/src/legacy/js/core/time.js b/addons/web/static/src/legacy/js/core/time.js index a5841d5b629..1234681ba6e 100644 --- a/addons/web/static/src/legacy/js/core/time.js +++ b/addons/web/static/src/legacy/js/core/time.js @@ -1,5 +1,6 @@ /** @odoo-module alias=web.time **/ +import { sprintf } from "@web/core/utils/strings"; import translation from "web.translation"; import utils from "web.utils"; @@ -189,7 +190,7 @@ function auto_str_to_date (value) { try { return str_to_time(value); } catch {} - throw new Error(_.str.sprintf(_t("'%s' is not a correct date, datetime nor time"), value)); + throw new Error(sprintf(_t("'%s' is not a correct date, datetime nor time"), value)); } function auto_date_to_str (value, type) { @@ -201,7 +202,7 @@ function auto_date_to_str (value, type) { case 'time': return time_to_str(value); default: - throw new Error(_.str.sprintf(_t("'%s' is not convertible to date, datetime nor time"), type)); + throw new Error(sprintf(_t("'%s' is not convertible to date, datetime nor time"), type)); } } diff --git a/addons/web/static/src/legacy/js/core/translation.js b/addons/web/static/src/legacy/js/core/translation.js index 9607bd81422..e6881dceda1 100644 --- a/addons/web/static/src/legacy/js/core/translation.js +++ b/addons/web/static/src/legacy/js/core/translation.js @@ -19,7 +19,7 @@ var TranslationDataBase = Class.extend(/** @lends instance.TranslationDataBase# set_bundle: function(translation_bundle) { var self = this; this.multi_lang = translation_bundle.multi_lang - var modules = _.keys(translation_bundle.modules); + var modules = Object.keys(translation_bundle.modules); modules.sort(); if (_.include(modules, "web")) { modules = ["web"].concat(_.without(modules, "web")); diff --git a/addons/web/static/src/legacy/js/core/utils.js b/addons/web/static/src/legacy/js/core/utils.js index 08fea185592..10fbd5603c5 100644 --- a/addons/web/static/src/legacy/js/core/utils.js +++ b/addons/web/static/src/legacy/js/core/utils.js @@ -9,6 +9,7 @@ import translation from "web.translation"; import { Component } from "@odoo/owl"; +import { sprintf } from "@web/core/utils/strings"; var _t = translation._t; var id = -1; @@ -633,7 +634,7 @@ const utils = { return sindent + node.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } else if (typeof(node.tag) !== 'string' || node.children && !(node.children instanceof Array) || node.attrs && !(node.attrs instanceof Object)) { throw new Error( - _.str.sprintf(_t("Node [%s] is not a JSONified XML node"), + sprintf(_t("Node [%s] is not a JSONified XML node"), JSON.stringify(node))); } for (var attr in node.attrs) { diff --git a/addons/web/static/src/legacy/js/fields/basic_fields.js b/addons/web/static/src/legacy/js/fields/basic_fields.js index 725323b5ed6..b34f63be877 100644 --- a/addons/web/static/src/legacy/js/fields/basic_fields.js +++ b/addons/web/static/src/legacy/js/fields/basic_fields.js @@ -15,6 +15,7 @@ import py_utils from "web.py_utils"; import session from "web.session"; import field_utils from "web.field_utils"; import utils from "web.utils"; +import { sprintf } from "@web/core/utils/strings"; var _t = core._t; var _lt = core._lt; @@ -1281,7 +1282,7 @@ var FieldFloatToggle = AbstractField.extend({ _nextValue: function () { var range = this.nodeOptions.range; var val = utils.closestNumber(this._getDisplayedValue(), range); - var index = _.indexOf(range, val); + var index = range.indexOf(val); if (index !== -1) { if (index + 1 < range.length) { return range[index + 1]; @@ -1539,7 +1540,7 @@ var AbstractFieldBinary = AbstractField.extend({ var file = file_node.files[0]; if (file.size > this.max_upload_size) { var msg = _t("The selected file exceed the maximum file size of %s."); - this.displayNotification({ title: _t("File upload"), message: _.str.sprintf(msg, utils.human_size(this.max_upload_size)), type: 'danger' }); + this.displayNotification({ title: _t("File upload"), message: sprintf(msg, utils.human_size(this.max_upload_size)), type: 'danger' }); return false; } utils.getDataURLFromFile(file).then(function (data) { diff --git a/addons/web/static/src/legacy/js/fields/field_utils.js b/addons/web/static/src/legacy/js/fields/field_utils.js index d7d5598d8f0..0c6ef19bb93 100644 --- a/addons/web/static/src/legacy/js/fields/field_utils.js +++ b/addons/web/static/src/legacy/js/fields/field_utils.js @@ -14,6 +14,7 @@ * conversions. */ +import { sprintf } from "@web/core/utils/strings"; import core from "web.core"; import dom from "web.dom"; import session from "web.session"; @@ -175,7 +176,7 @@ function formatFloat(value, field, options) { } else { precision = 2; } - var formatted = _.str.sprintf('%.' + precision + 'f', value || 0).split('.'); + var formatted = (value || 0).toFixed(precision).split("."); formatted[0] = utils.insert_thousand_seps(formatted[0]); return formatted.join(l10n.decimal_point); } @@ -210,18 +211,18 @@ function formatFloatFactor(value, field, options) { */ function formatFloatTime(value, field, options) { options = options || {}; - var pattern = options.noLeadingZeroHour ? '%1d:%02d' : '%02d:%02d'; - if (value < 0) { - value = Math.abs(value); - pattern = '-' + pattern; - } - var hour = Math.floor(value); - var min = Math.round((value % 1) * 60); - if (min === 60){ + var hour = Math.floor(Math.abs(value)); + var min = Math.round((Math.abs(value) % 1) * 60); + if (min === 60) { min = 0; hour = hour + 1; } - return _.str.sprintf(pattern, hour, min); + + const val = options.noLeadingZeroHour + ? String(hour) + ":" + String(min).padStart(2, "0") + : String(hour).padStart(2, "0") + ":" + String(min).padStart(2, "0"); + + return value < 0 ? "-" + val : val; } /** @@ -251,7 +252,7 @@ function formatInteger(value, field, options) { if (options.humanReadable && options.humanReadable(value)) { return utils.human_number(value, options.decimals, options.minDigits, options.formatterCallback); } - return utils.insert_thousand_seps(_.str.sprintf('%d', value)); + return utils.insert_thousand_seps(String(value)); } /** @@ -271,7 +272,7 @@ function formatInteger(value, field, options) { function formatMany2one(value, field, options) { if (!value) { value = ''; - } else if (_.isArray(value)) { + } else if (Array.isArray(value)) { // value is a pair [id, nameget] value = value[1]; } else { @@ -509,7 +510,7 @@ function parseDate(value, field, options) { return date; } } - throw new Error(_.str.sprintf(core._t("'%s' is not a correct date"), value)); + throw new Error(sprintf(core._t("'%s' is not a correct date"), value)); } /** @@ -562,7 +563,7 @@ function parseDateTime(value, field, options) { return datetime; } } - throw new Error(_.str.sprintf(core._t("'%s' is not a correct datetime"), value)); + throw new Error(sprintf(core._t("'%s' is not a correct datetime"), value)); } /** @@ -596,7 +597,7 @@ function parseNumber(value) { function parseFloat(value) { var parsed = parseNumber(value); if (isNaN(parsed)) { - throw new Error(_.str.sprintf(core._t("'%s' is not a correct float"), value)); + throw new Error(sprintf(core._t("'%s' is not a correct float"), value)); } return parsed; } @@ -644,7 +645,7 @@ function parseMonetary(value, field, options) { return parseFloat(value); } if (!value.includes(currency.symbol)) { - throw new Error(_.str.sprintf(core._t("'%s' is not a correct monetary field"), value)); + throw new Error(sprintf(core._t("'%s' is not a correct monetary field"), value)); } if (currency.position === 'before') { return parseFloat(value @@ -709,7 +710,7 @@ function parseInteger(value) { var parsed = parseNumber(value); // do not accept not numbers or float values if (isNaN(parsed) || parsed % 1 || parsed < -2147483648 || parsed > 2147483647) { - throw new Error(_.str.sprintf(core._t("'%s' is not a correct integer"), value)); + throw new Error(sprintf(core._t("'%s' is not a correct integer"), value)); } return parsed; } @@ -728,13 +729,13 @@ function parseInteger(value) { * number, the value is returned untouched. */ function parseMany2one(value) { - if (_.isArray(value)) { + if (Array.isArray(value)) { return { id: value[0], display_name: value[1], }; } - if (_.isNumber(value) || _.isString(value)) { + if (Number.isFinite(value) || _.isString(value)) { return { id: parseInt(value, 10), }; diff --git a/addons/web/static/src/legacy/js/fields/relational_fields.js b/addons/web/static/src/legacy/js/fields/relational_fields.js index bd42c66f7af..2eba90b6403 100644 --- a/addons/web/static/src/legacy/js/fields/relational_fields.js +++ b/addons/web/static/src/legacy/js/fields/relational_fields.js @@ -20,8 +20,7 @@ import dom from "web.dom"; import { FormViewDialog } from "@web/views/view_dialogs/form_view_dialog"; import { SelectCreateDialog } from "@web/views/view_dialogs/select_create_dialog"; import Domain from "web.Domain"; -import { sprintf } from "web.utils"; -import { escape } from "@web/core/utils/strings"; +import { escape, sprintf } from "@web/core/utils/strings"; var _t = core._t; var _lt = core._lt; @@ -37,7 +36,7 @@ var M2ODialog = Dialog.extend({ this.name = name; this.value = value; this._super(parent, { - title: _.str.sprintf(_t("New %s"), this.name), + title: sprintf(_t("New %s"), this.name), size: 'medium', buttons: [{ text: _t('Create'), @@ -423,7 +422,7 @@ var FieldMany2One = AbstractField.extend({ domain: this.record.getDomain({fieldName: this.name}), context: Object.assign({}, this.record.getContext(this.recordParams), context || {}), dynamicFilters: dynamicFilters || [], - title: _.str.sprintf((view === 'search' ? _t("Search: %s") : _t("Create: %s")), this.string), + title: sprintf((view === 'search' ? _t("Search: %s") : _t("Create: %s")), this.string), multiSelect: false, noCreate: !self.can_create, onSelected: function (records) { @@ -469,7 +468,7 @@ var FieldMany2One = AbstractField.extend({ return x[0]; }); dynamicFilters = [{ - description: _.str.sprintf(_t('Quick search: %s'), search_val), + description: sprintf(_t('Quick search: %s'), search_val), domain: [['id', 'in', ids]], }]; } @@ -596,7 +595,7 @@ var FieldMany2One = AbstractField.extend({ _renderReadonly: function () { this.$el.html(this._renderValueLines(true)); if (!this.noOpen && this.value) { - this.$el.attr('href', _.str.sprintf('#id=%s&model=%s', this.value.res_id, this.field.relation)); + this.$el.attr('href', `#id=${this.value.res_id}&model=${this.field.relation}`); this.$el.addClass('o_form_uri'); } }, @@ -1512,9 +1511,7 @@ var FieldSelection = AbstractField.extend({ this.values = this.record.specialData[this.name]; this.formatType = 'many2one'; } else { - this.values = _.reject(this.field.selection, function (v) { - return v[0] === false && v[1] === ''; - }); + this.values = this.field.selection.filter(v => !(v[0] === false && v[1] === '')); } this.values = [[false, this.attrs.placeholder || '']].concat(this.values); }, diff --git a/addons/web/static/src/legacy/js/views/basic/basic_model.js b/addons/web/static/src/legacy/js/views/basic/basic_model.js index 3057bae513a..e3be636bc26 100644 --- a/addons/web/static/src/legacy/js/views/basic/basic_model.js +++ b/addons/web/static/src/legacy/js/views/basic/basic_model.js @@ -82,6 +82,8 @@ * element, so it can be edited later. */ +import { intersection, unique } from "@web/core/utils/arrays"; +import { sprintf } from "@web/core/utils/strings"; import AbstractModel from "web.AbstractModel"; import concurrency from "web.concurrency"; import Context from "web.Context"; @@ -860,8 +862,8 @@ var BasicModel = AbstractModel.extend({ record.data[field.name] = null; if (field.type === 'many2one') { if (field.value) { - var id = _.isArray(field.value) ? field.value[0] : field.value; - var display_name = _.isArray(field.value) ? field.value[1] : undefined; + var id = Array.isArray(field.value) ? field.value[0] : field.value; + var display_name = Array.isArray(field.value) ? field.value[1] : undefined; dataPoint = self._makeDataPoint({ modelName: field.relation, data: { @@ -991,7 +993,7 @@ var BasicModel = AbstractModel.extend({ if (parent.static) { // x2Many case: the new record has been stored in _changes, as a // command so we remove the command(s) related to that record - parent._changes = _.filter(parent._changes, function (change) { + parent._changes = parent._changes.filter( change => { if (change.id === elementID && change.operation === 'ADD' && // For now, only an ADD command increases limits parent.tempLimitIncrement) { @@ -1072,7 +1074,7 @@ var BasicModel = AbstractModel.extend({ } if (dataType === 'list') { data.data = _.sortBy(data.data, function (d) { - return _.indexOf(resIDs, self.localData[d].res_id) + return resIDs.indexOf(self.localData[d].res_id); }); } } @@ -1247,9 +1249,7 @@ var BasicModel = AbstractModel.extend({ list.orderedBy[0].asc = !list.orderedBy[0].asc; } } else { - var orderedBy = _.reject(list.orderedBy, function (o) { - return o.name === fieldName; - }); + var orderedBy = list.orderedBy.filter(o => o.name !== fieldName); list.orderedBy = [{name: fieldName, asc: true}].concat(orderedBy); } @@ -1538,7 +1538,7 @@ var BasicModel = AbstractModel.extend({ if (options.notifyChange === false) { return Promise.all(defs).then(function () { - return Promise.resolve(_.keys(changes)); + return Promise.resolve(Object.keys(changes)); }); } @@ -1558,7 +1558,7 @@ var BasicModel = AbstractModel.extend({ self._performOnChange(record, onChangeFields, { viewType: options.viewType }) .then(function (result) { delete record._warning; - resolve(_.keys(changes).concat(Object.keys(result && result.value || {}))); + resolve(Object.keys(changes).concat(Object.keys(result && result.value || {}))); }).guardedCatch(function () { self._visitChildren(record, function (elem) { Object.assign(elem, initialData[elem.id]); @@ -1566,13 +1566,13 @@ var BasicModel = AbstractModel.extend({ reject(); }); } else { - resolve(_.keys(changes)); + resolve(Object.keys(changes)); } }).then(function (fieldNames) { return self._fetchSpecialData(record).then(function (fieldNames2) { // Return the names of the fields that changed (onchange or // associated special data change) - return _.union(fieldNames, fieldNames2); + return fieldNames.concat(fieldNames2); }); }); }); @@ -1729,7 +1729,7 @@ var BasicModel = AbstractModel.extend({ if (val) { // when the value isn't false, it can be either // an array [id, display_name] or just an id. - var data = _.isArray(val) ? + var data = Array.isArray(val) ? {id: val[0], display_name: val[1]} : {id: val}; if (!oldValue || (self.localData[oldValue].res_id !== data.id)) { @@ -1961,7 +1961,7 @@ var BasicModel = AbstractModel.extend({ list._forceM2MLink = true; // handle multiple add: command[2] may be a dict of values (1 // record added) or an array of dict of values - var data = _.isArray(command.ids) ? command.ids : [command.ids]; + var data = Array.isArray(command.ids) ? command.ids : [command.ids]; // name_create records for which there is no id (typically, could // be the case of a quick_create in a many2many_tags, so data.length @@ -2066,23 +2066,22 @@ var BasicModel = AbstractModel.extend({ // no 'ADD' operation for that dataPoint, as it would mean // that the record wasn't in the relation yet var idsToRemove = command.ids; - list._changes = _.reject(list._changes, function (change, index) { - var idInCommands = _.contains(command.ids, change.id); + + list._changes = list._changes.filter(change => { + var idInCommands = command.ids.includes(change.id); if (idInCommands && change.operation === 'ADD') { - idsToRemove = _.without(idsToRemove, change.id); + idsToRemove = idsToRemove.filter(id => id !== change.id); } - return idInCommands; + return !idInCommands; }); - _.each(idsToRemove, function (id) { + idsToRemove.forEach(id => { var operation = list._forceM2MUnlink ? 'FORGET': 'DELETE'; list._changes.push({operation: operation, id: id}); }); break; case 'DELETE_ALL': // first remove all pending 'ADD' operations - list._changes = _.reject(list._changes, function (change) { - return change.operation === 'ADD'; - }); + list._changes = list._changes.filter(change => change.operation !== 'ADD'); // then apply 'DELETE' on existing records return this._applyX2ManyChange(record, fieldName, { @@ -2393,7 +2392,7 @@ var BasicModel = AbstractModel.extend({ evalContext = evalContext || this._getEvalContext(element); evaluated[k] = new Domain(mod, evalContext).compute(evalContext); } catch (e) { - throw new Error(_.str.sprintf('for modifier "%s": %s', k, e.message)); + throw new Error(sprintf('for modifier "%s": %s', k, e.message)); } } return evaluated; @@ -2436,12 +2435,12 @@ var BasicModel = AbstractModel.extend({ const referenceFieldName = record.fields[fieldName].string; this.displayNotification({ - title: _.str.sprintf( + title: sprintf( _t(`'%s' is unsynchronized with '%s'.`), referenceFieldName, modelFieldName, ), - message: _.str.sprintf( + message: sprintf( _t(`If you change %s or %s, the synchronization will be reapplied and the data will be modified.`), modelFieldName, referenceFieldName, @@ -2512,7 +2511,7 @@ var BasicModel = AbstractModel.extend({ return this._rpc({ model: model, method: 'name_get', - args: [_.uniq(ids)], + args: [unique(ids)], context: list.context, }) .then(function (name_gets) { @@ -2539,7 +2538,7 @@ var BasicModel = AbstractModel.extend({ var self = this; options = options || {}; var fieldNames = options.fieldNames || record.getFieldNames(options); - fieldNames = _.uniq(fieldNames.concat(['display_name'])); + fieldNames = unique(fieldNames.concat(['display_name'])); return this._rpc({ model: record.model, method: 'read', @@ -2761,7 +2760,7 @@ var BasicModel = AbstractModel.extend({ */ _fetchRelatedData: function (list, toFetch, fieldName) { var self = this; - var ids = _.keys(toFetch); + var ids = Object.keys(toFetch || {}); for (var i = 0; i < ids.length; i++) { ids[i] = Number(ids[i]); } @@ -2772,7 +2771,7 @@ var BasicModel = AbstractModel.extend({ } var def; - var fieldNames = _.keys(fieldInfo.relatedFields); + var fieldNames = Object.keys(fieldInfo.relatedFields || {}); if (fieldNames.length) { var field = list.fields[fieldName]; def = this._rpc({ @@ -2782,12 +2781,14 @@ var BasicModel = AbstractModel.extend({ context: list.getContext({withoutRecordData: true}) || {}, }); } else { - def = Promise.resolve(_.map(ids, function (id) { - return {id:id}; - })); + def = Promise.resolve( + ids.map((id) => { + return { id: id }; + }) + ); } return def.then(function (result) { - var records = _.uniq(_.flatten(_.values(toFetch))); + var records = unique(Object.values(toFetch || {}).flat()); self._updateRecordsData(records, fieldName, result); }); }, @@ -3121,7 +3122,7 @@ var BasicModel = AbstractModel.extend({ _.each(list.data, function (groupIndex) { var group = self.localData[groupIndex]; var nextDataToFetch = self._getDataToFetch(group, fieldName); - _.each(_.keys(nextDataToFetch), function (id) { + _.each(Object.keys(nextDataToFetch), function (id) { if (toFetch[id]) { toFetch[id] = toFetch[id].concat(nextDataToFetch[id]); } else { @@ -3316,9 +3317,7 @@ var BasicModel = AbstractModel.extend({ list = this._applyX2ManyOperations(list); this._sortList(list); if (type === 'many2many' || list._forceM2MLink) { - var relRecordCreated = _.filter(relRecordAdded, function (rec) { - return typeof rec.res_id === 'string'; - }); + var relRecordCreated = relRecordAdded.filter(rec => typeof rec.res_id === 'string'); var realIDs = _.difference(list.res_ids, _.pluck(relRecordCreated, 'res_id')); // deliberately generate a single 'replace' command instead // of a 'delete' and a 'link' commands with the exact diff @@ -3342,7 +3341,7 @@ var BasicModel = AbstractModel.extend({ } else if (type === 'one2many') { var removedIds = _.difference(oldResIDs, list.res_ids); var addedIds = _.difference(list.res_ids, oldResIDs); - var keptIds = _.intersection(oldResIDs, list.res_ids); + var keptIds = intersection(oldResIDs, list.res_ids); // the didChange variable keeps track of the fact that at // least one id was updated @@ -3878,7 +3877,7 @@ var BasicModel = AbstractModel.extend({ } if (field.type === 'one2many' || field.type === 'many2many') { var ids; - if (!context[fieldName] || _.isArray(context[fieldName])) { // no dataPoint created yet + if (!context[fieldName] || Array.isArray(context[fieldName])) { // no dataPoint created yet ids = context[fieldName] ? context[fieldName].slice(0) : []; } else { relDataPoint = this._applyX2ManyOperations(this.localData[context[fieldName]]); @@ -3894,9 +3893,7 @@ var BasicModel = AbstractModel.extend({ * The webClient doesn't do literal id list comparison like ids == list * Only relevant in o2m: m2m does create actual records in db */ - ids = _.filter(ids, function (id) { - return typeof id !== 'string'; - }); + ids = ids.filter(id => typeof id !== 'string'); } context[fieldName] = ids; } @@ -3949,7 +3946,7 @@ var BasicModel = AbstractModel.extend({ } if (field.type === 'one2many' || field.type === 'many2many') { let ids; - if (!fieldValue || _.isArray(fieldValue)) { // no dataPoint created yet + if (!fieldValue || Array.isArray(fieldValue)) { // no dataPoint created yet ids = fieldValue ? fieldValue.slice(0) : []; } else { relDataPoint = self._applyX2ManyOperations(self.localData[fieldValue]); @@ -3965,13 +3962,11 @@ var BasicModel = AbstractModel.extend({ * The webClient doesn't do literal id list comparison like ids == list * Only relevant in o2m: m2m does create actual records in db */ - ids = _.filter(ids, function (id) { - return typeof id !== 'string'; - }); + ids = ids.filter(id => typeof id !== 'string'); } return ids; } - if (field.type === "properties" && _.isArray(fieldValue)) { + if (field.type === "properties" && Array.isArray(fieldValue)) { // remove deleted properties to be able // to filter based on empty properties field return fieldValue.filter(definition => !definition.definition_deleted); @@ -4193,7 +4188,7 @@ var BasicModel = AbstractModel.extend({ limit: type === 'record' ? 1 : (params.limit || Number.MAX_SAFE_INTEGER), loadMoreOffset: 0, model: params.modelName, - offset: params.offset || (type === 'record' ? _.indexOf(res_ids, res_id) : 0), + offset: params.offset || (type === 'record' ? res_ids.indexOf(res_id) : 0), openGroupByDefault: params.openGroupByDefault, orderedBy: params.orderedBy || [], orderedResIDs: params.orderedResIDs, @@ -4265,7 +4260,7 @@ var BasicModel = AbstractModel.extend({ if (parentRecord && parentRecord.viewType in parentRecord.fieldsInfo) { var originView = parentRecord.viewType; - fieldNames = _.union(fieldNames, Object.keys(parentRecord.fieldsInfo[originView])); + fieldNames = fieldNames.concat(Object.keys(parentRecord.fieldsInfo[originView])); fieldsInfo[targetView] = _.defaults({}, fieldsInfo[targetView], parentRecord.fieldsInfo[originView]); fields = _.defaults({}, fields, parentRecord.fields); } @@ -4594,7 +4589,7 @@ var BasicModel = AbstractModel.extend({ var many2ones = {}; var r; commands = commands || []; // handle false value - var isCommandList = commands.length && _.isArray(commands[0]); + var isCommandList = commands.length && Array.isArray(commands[0]); if (!isCommandList) { commands = [[6, false, commands]]; } @@ -4673,7 +4668,7 @@ var BasicModel = AbstractModel.extend({ }); // fetch many2ones display_name - _.each(_.keys(many2ones), function (name) { + _.each(Object.keys(many2ones), function (name) { defs.push(self._fetchNameGets(x2manyList, name)); }); @@ -4701,7 +4696,7 @@ var BasicModel = AbstractModel.extend({ } var record = self.localData[dataPointID]; var data = Object.assign({}, record.data, record._changes); - if (_.difference(fieldNames, _.keys(data)).length) { + if (_.difference(fieldNames, Object.keys(data)).length) { missingIDs.push(resId); } } @@ -4770,9 +4765,9 @@ var BasicModel = AbstractModel.extend({ var groupByField = list.groupedBy[0]; var rawGroupBy = groupByField.split(':')[0]; var fields = _.uniq(list.getFieldNames().concat(rawGroupBy)); - var orderedBy = _.filter(list.orderedBy, function (order) { + var orderedBy = list.orderedBy?.filter(order => { return order.name === rawGroupBy || list.fields[order.name].group_operator !== undefined; - }); + }) || []; var openGroupsLimit = list.groupsLimit || self.OPEN_GROUP_LIMIT; var expand = list.openGroupByDefault && options.fetchRecordsWithGroups; return this._rpc({ @@ -4924,7 +4919,7 @@ var BasicModel = AbstractModel.extend({ if (list.res_ids.length > list.limit && list.orderedBy.length) { if (!list.orderedResIDs) { var fieldNames = _.pluck(list.orderedBy, 'name'); - def = this._readMissingFields(list, _.filter(list.res_ids, _.isNumber), fieldNames, options.withoutRecordData); + def = this._readMissingFields(list, list.res_ids.filter(id => Number.isFinite(id)), fieldNames, options.withoutRecordData); } def.then(function () { self._sortList(list); @@ -4953,7 +4948,7 @@ var BasicModel = AbstractModel.extend({ var fieldNames = list.getFieldNames(); for (var i = list.offset; i < upperBound; i++) { var resId = currentResIDs[i]; - if (_.isNumber(resId)) { + if (Number.isFinite(resId)) { resIDs.push(resId); } } @@ -5045,7 +5040,7 @@ var BasicModel = AbstractModel.extend({ element.count = element.res_ids.length; } if (element.type === 'record') { - element.offset = _.indexOf(element.res_ids, element.res_id); + element.offset = element.res_ids.indexOf(element.res_id); } var loadOptions = _.pick(options, 'fieldNames', 'viewType'); return this._load(element, loadOptions).then(function (result) { diff --git a/addons/web/static/src/legacy/js/views/view_utils.js b/addons/web/static/src/legacy/js/views/view_utils.js index 26db6e11047..b1a3b734330 100644 --- a/addons/web/static/src/legacy/js/views/view_utils.js +++ b/addons/web/static/src/legacy/js/views/view_utils.js @@ -1,6 +1,7 @@ /** @odoo-module alias=web.viewUtils **/ +import { unique } from '@web/core/utils/arrays'; import * as dom from 'web.dom'; import * as utils from 'web.utils'; @@ -93,7 +94,7 @@ var viewUtils = { }; if (options && options.extraClass) { var classes = btnOptions.attrs.class ? btnOptions.attrs.class.split(' ') : []; - btnOptions.attrs.class = _.uniq(classes.concat(options.extraClass.split(' '))).join(' '); + btnOptions.attrs.class = unique(classes.concat(options.extraClass.split(' '))).join(' '); } var str = (node.attrs.string || '').replace(/_/g, ''); if (str) { diff --git a/addons/web/static/src/legacy/js/widgets/colorpicker.js b/addons/web/static/src/legacy/js/widgets/colorpicker.js index bb307e55827..8c7594d220c 100644 --- a/addons/web/static/src/legacy/js/widgets/colorpicker.js +++ b/addons/web/static/src/legacy/js/widgets/colorpicker.js @@ -136,14 +136,17 @@ var ColorpickerWidget = Widget.extend({ // Update inputs _.each(this.colorComponents, function (value, color) { - self.$(_.str.sprintf('.o_%s_input', color)).val(value); + self.$(`.o_${color}_input`).val(value); }); // Update preview this.$('.o_color_preview').css('background-color', this.colorComponents.cssColor); // Update picker area and picker pointer position - this.$colorpickerArea.css('background-color', _.str.sprintf('hsl(%s, 100%%, 50%%)', this.colorComponents.hue)); + this.$colorpickerArea.css( + "background-color", + `hsl(${this.colorComponents.hue}, 100%, 50%)` + ); var top = (100 - this.colorComponents.lightness) * this.$colorpickerArea.height() / 100; var left = this.colorComponents.saturation * this.$colorpickerArea.width() / 100; this.$colorpickerPointer.css({ diff --git a/addons/web/static/src/legacy/js/widgets/domain_selector.js b/addons/web/static/src/legacy/js/widgets/domain_selector.js index 8183fc5115d..e4c4d18b055 100644 --- a/addons/web/static/src/legacy/js/widgets/domain_selector.js +++ b/addons/web/static/src/legacy/js/widgets/domain_selector.js @@ -251,7 +251,7 @@ var DomainTree = DomainNode.extend({ * false otherwise (the afterNode was not found) */ _addChild: function (domain, afterNode) { - var i = afterNode ? _.indexOf(this.children, afterNode) : this.children.length; + var i = afterNode ? this.children.indexOf(afterNode) : this.children.length; if (i < 0) return false; this.children.splice(i+1, 0, instantiateNode(this, this.model, domain, this.options)); @@ -355,7 +355,7 @@ var DomainTree = DomainNode.extend({ * widget does not own the child) */ _removeChild: function (oldChild) { - var i = _.indexOf(this.children, oldChild); + var i = this.children.indexOf(oldChild); if (i < 0) return false; this.children[i].destroy(); @@ -708,7 +708,7 @@ var DomainLeaf = DomainNode.extend({ // Adapt display value and operator for rendering this.displayValue = this.value; try { - if (selectedField && !selectedField.relation && !_.isArray(this.value)) { + if (selectedField && !selectedField.relation && !Array.isArray(this.value)) { this.displayValue = field_utils.format[selectedField.type](this.value, selectedField); } } catch {/**/} @@ -819,9 +819,9 @@ var DomainLeaf = DomainNode.extend({ this.operator = this.operator === "not set" ? "=" : "!="; this.value = false; } else if (_.contains(["in", "not in"], this.operator)) { - this.value = _.isArray(this.value) ? this.value : this.value ? ("" + this.value).split(",") : []; + this.value = Array.isArray(this.value) ? this.value : this.value ? ("" + this.value).split(",") : []; } else { - if (_.isArray(this.value)) { + if (Array.isArray(this.value)) { this.value = this.value.join(","); } this._changeValue(this.value, true); @@ -868,7 +868,7 @@ var DomainLeaf = DomainNode.extend({ // Never display "true" or "false" strings from boolean value if (_.isBoolean(this.value)) { this.value = ""; - } else if (_.isObject(this.value) && !_.isArray(this.value)) { // Can be object if parsed to x2x representation + } else if (_.isObject(this.value) && !Array.isArray(this.value)) { // Can be object if parsed to x2x representation this.value = this.value.id || value || ""; } } @@ -971,7 +971,7 @@ var DomainLeaf = DomainNode.extend({ if (e.type === "keyup" && e.which !== $.ui.keyCode.ENTER) return; if (!_.contains(["not in", "in"], this.operator)) return; - var values = _.isArray(this.value) ? this.value.slice() : []; + var values = Array.isArray(this.value) ? this.value.slice() : []; var $input = this.$(".o_domain_leaf_value_tags input"); var val = $input.val().trim(); @@ -982,7 +982,7 @@ var DomainLeaf = DomainNode.extend({ } }, on_remove_tag: function (e) { - var values = _.isArray(this.value) ? this.value.slice() : []; + var values = Array.isArray(this.value) ? this.value.slice() : []; var val = this.$(e.currentTarget).data("value"); var index = values.indexOf(val); diff --git a/addons/web/static/src/legacy/js/widgets/model_field_selector_popover.js b/addons/web/static/src/legacy/js/widgets/model_field_selector_popover.js index 81752970b23..85c0a77a170 100644 --- a/addons/web/static/src/legacy/js/widgets/model_field_selector_popover.js +++ b/addons/web/static/src/legacy/js/widgets/model_field_selector_popover.js @@ -245,7 +245,7 @@ var ModelFieldSelectorPopover = Widget.extend({ }).bind(this)); } return def.then((function () { - return _.filter(modelFieldsCache.cache[model], function (f) { + return modelFieldsCache.cache[model].filter(f => { return (!filters.searchable || f.searchable) && self.options.filter(f); }); }).bind(this)); @@ -429,7 +429,12 @@ var ModelFieldSelectorPopover = Widget.extend({ var page = this.pages.at(-1); this.$(".o_field_selector_popover_header .o_field_selector_title").text(this._getTitle()); - var lines = _.filter(page, this.options.filter); + var lines = page.filter((item) => { + return Object.keys(this.options.filter).every((key) => { + return item[key] === this.options.filter[key]; + }); + }); + if (this.searchValue) { lines = fuzzyLookup(this.searchValue, lines, (l) => l.string); } diff --git a/addons/web/static/src/views/fields/timezone_mismatch/timezone_mismatch_field.js b/addons/web/static/src/views/fields/timezone_mismatch/timezone_mismatch_field.js index fde80a6b93a..9e997b8ef7f 100644 --- a/addons/web/static/src/views/fields/timezone_mismatch/timezone_mismatch_field.js +++ b/addons/web/static/src/views/fields/timezone_mismatch/timezone_mismatch_field.js @@ -27,8 +27,12 @@ export class TimezoneMismatchField extends SelectionField { if (userOffset && this.props.record.data[this.props.name]) { const offset = -new Date().getTimezoneOffset(); let browserOffset = offset < 0 ? "-" : "+"; - browserOffset += _.str.sprintf("%02d", Math.abs(offset / 60)); - browserOffset += _.str.sprintf("%02d", Math.abs(offset % 60)); + browserOffset += Math.abs(offset / 60) + .toFixed(0) + .padStart(2, "0"); + browserOffset += Math.abs(offset % 60) + .toFixed(0) + .padStart(2, "0"); return browserOffset !== userOffset; } else if (!this.props.record.data[this.props.name]) { return true; diff --git a/addons/web/static/tests/legacy/core/py_utils_tests.js b/addons/web/static/tests/legacy/core/py_utils_tests.js index a804d2d35a3..ccaf1f7fe02 100644 --- a/addons/web/static/tests/legacy/core/py_utils_tests.js +++ b/addons/web/static/tests/legacy/core/py_utils_tests.js @@ -85,10 +85,20 @@ QUnit.module('core', function () { return String(d.getFullYear()) + '-01-30'; }); - check("time.strftime('%Y-%m-%d %H:%M:%S')", function(d) { - return _.str.sprintf('%04d-%02d-%02d %02d:%02d:%02d', - d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(), - d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds()); + check("time.strftime('%Y-%m-%d %H:%M:%S')", function (d) { + return ( + [ + String(d.getUTCFullYear()).padStart(4, "0"), + String(d.getUTCMonth() + 1).padStart(2, "0"), + String(d.getUTCDate()).padStart(2, "0"), + ].join("-") + + " " + + [ + String(d.getUTCHours()).padStart(2, "0"), + String(d.getUTCMinutes()).padStart(2, "0"), + String(d.getUTCSeconds()).padStart(2, "0"), + ].join(":") + ); }); }); @@ -98,8 +108,11 @@ QUnit.module('core', function () { var check = makeTimeCheck(assert, pyUtils); check("context_today().strftime('%Y-%m-%d')", function(d) { - return String(_.str.sprintf('%04d-%02d-%02d', - d.getFullYear(), d.getMonth() + 1, d.getDate())); + return [ + String(d.getFullYear()).padStart(4, "0"), + String(d.getMonth() + 1).padStart(2, "0"), + String(d.getDate()).padStart(2, "0"), + ].join("-"); }); }); @@ -709,8 +722,11 @@ QUnit.module('core', function () { }); var d = new Date(); - var today = _.str.sprintf("%04d-%02d-%02d", - d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate()); + var today = [ + String(d.getUTCFullYear()).padStart(4, "0"), + String(d.getUTCMonth() + 1).padStart(2, "0"), + String(d.getUTCDate()).padStart(2, "0"), + ].join("-"); assert.deepEqual(result.domain, [ ["type", "=", "contract"], "|", ["state", "in", ["open", "draft"]], @@ -777,8 +793,11 @@ QUnit.module('core', function () { }); var date = new Date(); - var today = _.str.sprintf("%04d-%02d-%02d", - date.getFullYear(), date.getMonth() + 1, date.getDate()); + var today = [ + String(date.getFullYear()).padStart(4, "0"), + String(date.getMonth() + 1).padStart(2, "0"), + String(date.getDate()).padStart(2, "0"), + ].join("-"); assert.deepEqual(result.domain, [ ['state', '!=', 'cancel'], ['opening_date', '>', today] @@ -794,11 +813,17 @@ QUnit.module('core', function () { contexts: [], }); var date = new Date(); - var today = _.str.sprintf("%04d-%02d-%02d", - date.getFullYear(), date.getMonth() + 1, date.getDate()); + var today = [ + String(date.getFullYear()).padStart(4, "0"), + String(date.getMonth() + 1).padStart(2, "0"), + String(date.getDate()).padStart(2, "0"), + ].join("-"); date.setDate(date.getDate() - 15); - var ago_15_d = _.str.sprintf("%04d-%02d-%02d", - date.getFullYear(), date.getMonth() + 1, date.getDate()); + var ago_15_d = [ + String(date.getFullYear()).padStart(4, "0"), + String(date.getMonth() + 1).padStart(2, "0"), + String(date.getDate()).padStart(2, "0"), + ].join("-"); assert.deepEqual(result.domain, [ ['type', '=', 'in'], ['day', '<=', today], diff --git a/addons/web/static/tests/legacy/helpers/mock_server.js b/addons/web/static/tests/legacy/helpers/mock_server.js index b792c9d44ef..a77256d89d7 100644 --- a/addons/web/static/tests/legacy/helpers/mock_server.js +++ b/addons/web/static/tests/legacy/helpers/mock_server.js @@ -381,14 +381,14 @@ var MockServer = Class.extend({ var v = pyUtils.py_eval(mod, {context: pyevalContext}) ? true: false; if (inTreeView && !inListHeader && a === 'invisible') { modifiers.column_invisible = v; - } else if (v || !(a in modifiers) || !_.isArray(modifiers[a])) { + } else if (v || !(a in modifiers) || !Array.isArray(modifiers[a])) { modifiers[a] = v; } } }); _.each(modifiersNames, function (a) { - if (a in modifiers && (!!modifiers[a] === false || (_.isArray(modifiers[a]) && !modifiers[a].length))) { + if (a in modifiers && (!!modifiers[a] === false || (Array.isArray(modifiers[a]) && !modifiers[a].length))) { delete modifiers[a]; } }); @@ -470,7 +470,7 @@ var MockServer = Class.extend({ * @returns {Object[]} a list of records */ _getRecords: function (model, domain, { active_test = true } = {}) { - if (!_.isArray(domain)) { + if (!Array.isArray(domain)) { throw new Error("MockServer._getRecords: given domain has to be an array."); } @@ -517,9 +517,7 @@ var MockServer = Class.extend({ } return criterion; }); - records = _.filter(records, function (record) { - return self._evaluateDomain(domain, record); - }); + records = records.filter(record => self._evaluateDomain(domain, record)); } return records; @@ -1217,7 +1215,7 @@ var MockServer = Class.extend({ } } var key = [model, viewID, viewType].join(','); - var arch = self.archs[key] || _.find(self.archs, function (_v, k) { + var arch = self.archs[key] || Object.keys(self.archs).find(k => { var ka = k.split(','); viewID = parseInt(ka[1], 10); return ka[0] === model && ka[2] === viewType; @@ -1252,7 +1250,7 @@ var MockServer = Class.extend({ else if (!ids) { return [] } - if (!_.isArray(ids)) { + if (!Array.isArray(ids)) { ids = [ids]; } var records = this.data[model].records; @@ -1299,9 +1297,7 @@ var MockServer = Class.extend({ var domain = (args && args[1]) || _kwargs.args || []; var records = this._getRecords(model, domain); if (str.length) { - records = _.filter(records, function (record) { - return record.display_name.indexOf(str) !== -1; - }); + records = records.filter(record => record.display_name.indexOf(str) !== -1); } var result = _.map(records, function (record) { return [record.id, record.display_name]; @@ -1380,7 +1376,7 @@ var MockServer = Class.extend({ _mockRead: function (model, args, _kwargs) { var self = this; var ids = args[0]; - if (!_.isArray(ids)) { + if (!Array.isArray(ids)) { ids = [ids]; } var fields = args[1] && args[1].length ? _.uniq(args[1].concat(['id'])) : Object.keys(this.data[model].fields); @@ -1467,7 +1463,7 @@ var MockServer = Class.extend({ // if no fields have been given, the server picks all stored fields if (kwargs.fields.length === 0) { - aggregatedFields = _.keys(this.data[model].fields); + aggregatedFields = Object.keys(this.data[model].fields); } var groupByFieldNames = _.map(groupBy, function (groupByField) { @@ -1475,7 +1471,7 @@ var MockServer = Class.extend({ }); // filter out non existing fields - aggregatedFields = _.filter(aggregatedFields, function (name) { + aggregatedFields = aggregatedFields.filter(name => { return name in self.data[model].fields && !(_.contains(groupByFieldNames,name)); }); @@ -1827,7 +1823,7 @@ var MockServer = Class.extend({ var records = this._getRecords(args.model, args.domain || [], { active_test, }); - var fields = args.fields && args.fields.length ? args.fields : _.keys(this.data[args.model].fields); + var fields = args.fields && args.fields.length ? args.fields : Object.keys(this.data[args.model].fields); var nbRecords = records.length; var offset = args.offset || 0; if (args.sort) { @@ -1870,12 +1866,10 @@ var MockServer = Class.extend({ */ _mockUnlink: function (model, args) { var ids = args[0]; - if (!_.isArray(ids)) { + if (!Array.isArray(ids)) { ids = [ids]; } - this.data[model].records = _.reject(this.data[model].records, function (record) { - return _.contains(ids, record.id); - }); + this.data[model].records = this.data[model].records.filter(record => !_.contains(ids, record.id)); // update value of relationnal fields pointing to the deleted records _.each(this.data, function (d) { diff --git a/addons/web/static/tests/legacy/helpers/test_utils_mock.js b/addons/web/static/tests/legacy/helpers/test_utils_mock.js index 22aded6025c..989ea17fc8b 100644 --- a/addons/web/static/tests/legacy/helpers/test_utils_mock.js +++ b/addons/web/static/tests/legacy/helpers/test_utils_mock.js @@ -146,7 +146,7 @@ async function _getMockedOwlEnv(params, mockServer) { delete servicesToDeploy[serviceName]; services[serviceName].start(); } else { - const serviceNames = _.keys(servicesToDeploy); + const serviceNames = Object.keys(servicesToDeploy); if (serviceNames.length) { console.warn("Non loaded services:", serviceNames); } diff --git a/addons/web/static/tests/views/fields/one2many_field_tests.js b/addons/web/static/tests/views/fields/one2many_field_tests.js index 6c052616fb8..252df7078f5 100644 --- a/addons/web/static/tests/views/fields/one2many_field_tests.js +++ b/addons/web/static/tests/views/fields/one2many_field_tests.js @@ -2303,7 +2303,7 @@ QUnit.module("Fields", (hooks) => { if (args.method === "write") { saveCount++; const nbCommands = args.args[1].p.length; - const nbLinkCommands = _.filter(args.args[1].p, function (command) { + const nbLinkCommands = args.args[1].p.filter((command) => { return command[0] === 4; }).length; switch (saveCount) { diff --git a/addons/web_editor/static/src/js/common/ace.js b/addons/web_editor/static/src/js/common/ace.js index 8faac6b9e7d..2065b5fdeee 100644 --- a/addons/web_editor/static/src/js/common/ace.js +++ b/addons/web_editor/static/src/js/common/ace.js @@ -7,6 +7,7 @@ import dom from "web.dom"; import Dialog from "web.Dialog"; import Widget from "web.Widget"; import localStorage from "web.local_storage"; +import { sprintf } from "@web/core/utils/strings"; var _t = core._t; @@ -283,9 +284,9 @@ var ViewEditor = Widget.extend({ if (typeof this.viewKey === "number") { initResID = this.viewKey; } else { - var view = _.findWhere(this.views, {xml_id: this.viewKey}); + var view = Object.values(this.views).find(view => view.xml_id === this.viewKey); if (!view) { - view = _.findWhere(this.views, {key: this.viewKey}); + view = Object.values(this.views).find(view => view.key === this.viewKey); } initResID = view.id; } @@ -425,11 +426,11 @@ var ViewEditor = Widget.extend({ this.aceEditor.setSession(editingSession); if (this.currentType === 'xml') { - this.$viewID.text(_.str.sprintf(_t("Template ID: %s"), this.views[resID].key)); + this.$viewID.text(sprintf(_t("Template ID: %s"), this.views[resID].key)); } else if (this.currentType === 'scss') { - this.$viewID.text(_.str.sprintf(_t("SCSS file: %s"), resID)); + this.$viewID.text(sprintf(_t("SCSS file: %s"), resID)); } else { - this.$viewID.text(_.str.sprintf(_t("JS file: %s"), resID)); + this.$viewID.text(sprintf(_t("JS file: %s"), resID)); } const isCustomized = this._isCustomResource(resID); this.$lists[this.currentType].select2('val', resID); @@ -518,9 +519,7 @@ var ViewEditor = Widget.extend({ function _processViews(views) { // Only keep the active views and index them by ID. - Object.assign(this.views, _.indexBy(_.filter(views, function (view) { - return view.active; - }), 'id')); + Object.assign(this.views, _.indexBy(views.filter(view => view.active), 'id')); // Initialize a 0 level for each view and assign them an array containing their children. var self = this; @@ -893,7 +892,7 @@ var ViewEditor = Widget.extend({ label: bundleInfos[0], }).appendTo($list); _.each(bundleInfos[1], function (dataInfo) { - var name = dataInfo.url.substring(_.lastIndexOf(dataInfo.url, '/') + 1, dataInfo.url.length - lettersToRemove); + var name = dataInfo.url.substring(dataInfo.url.lastIndexOf('/') + 1, dataInfo.url.length - lettersToRemove); $optgroup.append($('
', {text: _.str.sprintf(_t("Do you want to install the %s App?"), name)}).append( + $content: $('
', {text: sprintf(_t("Do you want to install the %s App?"), name)}).append( $('', { target: '_blank', href: '/web#id=' + moduleID + '&view_type=form&model=ir.module.module&action=base.open_module_tree', @@ -3817,7 +3818,7 @@ var SnippetsMenu = Widget.extend({ new Dialog(this, { size: 'medium', title: _t('Confirmation'), - $content: $('

' + _.str.sprintf(_t("Are you sure you want to delete the snippet: %s?"), $snippet.attr('name')) + '

'), + $content: $('

' + sprintf(_t("Are you sure you want to delete the snippet: %s?"), $snippet.attr('name')) + '

'), buttons: [{ text: _t("Yes"), close: true, diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js index 6cf6736cf70..7df7b78b3c6 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -33,6 +33,7 @@ import { } from "web_editor.image_processing"; import * as OdooEditorLib from "@web_editor/js/editor/odoo-editor/src/OdooEditor"; import {SIZES, MEDIAS_BREAKPOINTS} from "@web/core/ui/ui_service"; +import { sprintf } from "@web/core/utils/strings"; var qweb = core.qweb; var _t = core._t; @@ -5771,9 +5772,9 @@ const ImageHandlerOption = SnippetOptionWidget.extend({ 1024: '1024px', 1920: '1920px', }; - widths[img.naturalWidth] = _.str.sprintf(_t("%spx"), img.naturalWidth); - widths[optimizedWidth] = _.str.sprintf(_t("%dpx (Suggested)"), optimizedWidth); - widths[maxWidth] = _.str.sprintf(_t("%dpx (Original)"), maxWidth); + widths[img.naturalWidth] = sprintf(_t("%spx"), img.naturalWidth); + widths[optimizedWidth] = sprintf(_t("%dpx (Suggested)"), optimizedWidth); + widths[maxWidth] = sprintf(_t("%dpx (Original)"), maxWidth); return Object.entries(widths) .filter(([width]) => width <= maxWidth) .sort(([v1], [v2]) => v1 - v2); @@ -7815,7 +7816,7 @@ registry.SnippetSave = SnippetOptionWidget.extend({ reloadEditor: true, invalidateSnippetCache: true, onSuccess: async () => { - const defaultSnippetName = _.str.sprintf(_t("Custom %s"), this.data.snippetName); + const defaultSnippetName = sprintf(_t("Custom %s"), this.data.snippetName); const targetCopyEl = this.$target[0].cloneNode(true); delete targetCopyEl.dataset.name; // By the time onSuccess is called after request_save, the diff --git a/addons/web_editor/static/tests/test_utils.js b/addons/web_editor/static/tests/test_utils.js index 7827ca43ee6..f98b3586f7d 100644 --- a/addons/web_editor/static/tests/test_utils.js +++ b/addons/web_editor/static/tests/test_utils.js @@ -7,6 +7,7 @@ import * as OdooEditorLib from "@web_editor/js/editor/odoo-editor/src/OdooEditor import Widget from "web.Widget"; import Wysiwyg from "web_editor.wysiwyg"; import options from "web_editor.snippets.options"; +import { range } from "@web/core/utils/numbers"; const COLOR_PICKER_TEMPLATE = ` @@ -356,7 +357,7 @@ var keyboardMap = { "91": "OS_KEY", // 'left command': Windows Key (Windows) or Command Key (Mac) "93": "CONTEXT_MENU", // 'right command' }; -_.each(_.range(40, 127), function (keyCode) { +_.each(range(40, 127), function (keyCode) { if (!keyboardMap[keyCode]) { keyboardMap[keyCode] = String.fromCharCode(keyCode); } diff --git a/addons/web_unsplash/static/src/components/media_dialog/image_selector.js b/addons/web_unsplash/static/src/components/media_dialog/image_selector.js index c0eb0d93de1..2c9c5381853 100644 --- a/addons/web_unsplash/static/src/components/media_dialog/image_selector.js +++ b/addons/web_unsplash/static/src/components/media_dialog/image_selector.js @@ -8,6 +8,7 @@ import { useService } from '@web/core/utils/hooks'; import { uploadService, AUTOCLOSE_DELAY } from '@web_editor/components/upload_progress_toast/upload_service'; import { useState, Component } from "@odoo/owl"; +import { sprintf } from '@web/core/utils/strings'; class UnsplashCredentials extends Component { setup() { @@ -224,8 +225,8 @@ patch(uploadService, 'upload_service_unsplash', { const file = service.addFile({ id: service.fileId, name: records.length > 1 ? - _.str.sprintf(env._t("Uploading %s '%s' images."), records.length, records[0].query) : - _.str.sprintf(env._t("Uploading '%s' image."), records[0].query), + sprintf(env._t("Uploading %s '%s' images."), records.length, records[0].query) : + sprintf(env._t("Uploading '%s' image."), records[0].query), size: null, progress: 0, }); diff --git a/addons/website/static/src/components/ace_editor/ace_editor.js b/addons/website/static/src/components/ace_editor/ace_editor.js index 779213d92fd..b2ced1cd324 100644 --- a/addons/website/static/src/components/ace_editor/ace_editor.js +++ b/addons/website/static/src/components/ace_editor/ace_editor.js @@ -36,7 +36,7 @@ export const WebsiteAceEditor = AceEditor.extend({ // We need to find the new ID (either because the view became // specific or because its parent was edited too and the view // got copy/unlink). - const selectedView = _.findWhere(this.views, {id: this._getSelectedResource()}); + const selectedView = Object.values(this.views).find(view => view.id === this._getSelectedResource()); let context; this.trigger_up('context_get', { callback: (ctx) => { diff --git a/addons/website/static/src/js/backend/dashboard.js b/addons/website/static/src/js/backend/dashboard.js index 9693a6bf98e..f55ef0ed248 100644 --- a/addons/website/static/src/js/backend/dashboard.js +++ b/addons/website/static/src/js/backend/dashboard.js @@ -55,7 +55,7 @@ var Dashboard = AbstractAction.extend({ return Promise.all([loadBundle(this), this._super()]).then(function() { return self.fetch_data(); }).then(function(){ - var website = _.findWhere(self.websites, {selected: true}); + var website = self.websites.find(w => w.selected); self.website_id = website ? website.id : false; }); }, @@ -102,7 +102,7 @@ var Dashboard = AbstractAction.extend({ on_go_to_website: function (ev) { ev.preventDefault(); - var website = _.findWhere(this.websites, {selected: true}); + var website = this.websites.find(website => website.selected); window.location.replace(`/web#action=website.website_preview&website_id=${website.id}`); }, diff --git a/addons/website/static/src/js/content/snippets.animation.js b/addons/website/static/src/js/content/snippets.animation.js index 9c1e0bd083b..96dc2d9c84e 100644 --- a/addons/website/static/src/js/content/snippets.animation.js +++ b/addons/website/static/src/js/content/snippets.animation.js @@ -921,7 +921,7 @@ registry.socialShare = publicWidget.Widget.extend({ 'twitter': 'https://twitter.com/intent/tweet?original_referer=' + url + '&text=' + encodeURIComponent(title + hashtags + ' - ') + url, 'linkedin': 'https://www.linkedin.com/sharing/share-offsite/?url=' + url, }; - if (!_.contains(_.keys(socialNetworks), social)) { + if (!_.contains(Object.keys(socialNetworks), social)) { return; } var wHeight = 500; diff --git a/addons/website/static/src/js/content/website_root.js b/addons/website/static/src/js/content/website_root.js index f51d5932200..2093628d9ce 100644 --- a/addons/website/static/src/js/content/website_root.js +++ b/addons/website/static/src/js/content/website_root.js @@ -177,7 +177,7 @@ export const WebsiteRoot = publicRootData.PublicRoot.extend(KeyboardNavigationMi url: encodeURIComponent($target.attr('href').replace(/[&?]edit_translations[^&?]+/, '')), hash: encodeURIComponent(window.location.hash) }; - window.location.href = _.str.sprintf("/website/lang/%(lang)s?r=%(url)s%(hash)s", redirect); + window.location.href = `/website/lang/${redirect.lang}?r=${redirect.url}${redirect.hash}`; }, /** * @private diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js index 6ae2e5b0307..2670d072e97 100644 --- a/addons/website/static/src/js/editor/snippets.options.js +++ b/addons/website/static/src/js/editor/snippets.options.js @@ -11,6 +11,7 @@ import wLinkPopoverWidget from "@website/js/widgets/link_popover_widget"; import wUtils from "website.utils"; import {isImageSupportedForStyle} from "web_editor.image_processing"; import "website.s_popup_options"; +import { range } from "@web/core/utils/numbers"; var _t = core._t; var qweb = core.qweb; @@ -1387,7 +1388,7 @@ options.registry.OptionsTab = options.Class.extend({ await this._super(...arguments); const extraSaturationRangeEl = uiFragment.querySelector(`we-range[data-param=${this.GRAY_PARAMS.EXTRA_SATURATION}]`); if (extraSaturationRangeEl) { - const baseGrays = _.range(100, 1000, 100).map(id => { + const baseGrays = range(100, 1000, 100).map(id => { const gray = weUtils.getCSSVariableValue(`base-${id}`); const grayRGB = ColorpickerWidget.convertCSSColorToRgba(gray); const hsl = ColorpickerWidget.convertRgbToHsl(grayRGB.red, grayRGB.green, grayRGB.blue); diff --git a/addons/website/static/src/js/tours/tour_utils.js b/addons/website/static/src/js/tours/tour_utils.js index 3fea196f7f4..f309fed103d 100644 --- a/addons/website/static/src/js/tours/tour_utils.js +++ b/addons/website/static/src/js/tours/tour_utils.js @@ -3,6 +3,7 @@ import {_t} from "web.core"; import {Markup} from "web.utils"; import { registry } from "@web/core/registry"; +import { sprintf } from "@web/core/utils/strings"; function addMedia(position = "right") { return { @@ -104,7 +105,7 @@ function changeOption(optionName, weName = '', optionTooltipLabel = '', position const option_block = `${noPalette} we-customizeblock-option[class='snippet-option-${optionName}']`; return { trigger: `${option_block} ${weName}, ${option_block} [title='${weName}']`, - content: Markup(_.str.sprintf(_t("Click on this option to change the %s of the block."), optionTooltipLabel)), + content: Markup(sprintf(_t("Click on this option to change the %s of the block."), optionTooltipLabel)), position: position, run: "click", }; @@ -115,7 +116,7 @@ function selectNested(trigger, optionName, alt_trigger = null, optionTooltipLabe const option_block = `${noPalette} we-customizeblock-option[class='snippet-option-${optionName}']`; return { trigger: trigger, - content: Markup(_.str.sprintf(_t("Select a %s."), optionTooltipLabel)), + content: Markup(sprintf(_t("Select a %s."), optionTooltipLabel)), alt_trigger: alt_trigger == null ? undefined : `${option_block} ${alt_trigger}`, position: position, run: 'click', @@ -132,7 +133,7 @@ function changePaddingSize(direction) { } return { trigger: `iframe .oe_overlay.ui-draggable.o_we_overlay_sticky.oe_active .o_handle.${paddingDirection}`, - content: Markup(_.str.sprintf(_t("Slide this button to change the %s padding"), direction)), + content: Markup(sprintf(_t("Slide this button to change the %s padding"), direction)), consumeEvent: 'mousedown', position: position, }; @@ -229,7 +230,7 @@ function dragNDrop(snippet, position = "bottom") { return { trigger: `#oe_snippets .oe_snippet[name="${snippet.name}"] .oe_snippet_thumbnail:not(.o_we_already_dragging)`, extra_trigger: ".o_website_preview.editor_enable.editor_has_snippets", - content: Markup(_.str.sprintf(_t("Drag the %s building block and drop it at the bottom of the page."), snippet.name)), + content: Markup(sprintf(_t("Drag the %s building block and drop it at the bottom of the page."), snippet.name)), position: position, // Normally no main snippet can be dropped in the default footer but // targeting it allows to force "dropping at the end of the page". diff --git a/addons/website/static/src/js/utils.js b/addons/website/static/src/js/utils.js index 5e5189bd78b..0ec09344e8f 100644 --- a/addons/website/static/src/js/utils.js +++ b/addons/website/static/src/js/utils.js @@ -1,5 +1,6 @@ /** @odoo-module alias=website.utils **/ +import { intersection } from "@web/core/utils/arrays"; import core from "web.core"; const { qweb, _t } = core; @@ -186,7 +187,7 @@ function prompt(options, _qweb) { btn_secondary_title: _t('Cancel'), }, options || {}); - var type = _.intersection(Object.keys(options), ['input', 'textarea', 'select']); + var type = intersection(Object.keys(options), ['input', 'textarea', 'select']); type = type.length ? type[0] : 'input'; options.field_type = type; options.field_name = options.field_name || options[type]; diff --git a/addons/website/static/src/snippets/s_facebook_page/options.js b/addons/website/static/src/snippets/s_facebook_page/options.js index 45d7b020f34..259617b244c 100644 --- a/addons/website/static/src/snippets/s_facebook_page/options.js +++ b/addons/website/static/src/snippets/s_facebook_page/options.js @@ -1,5 +1,6 @@ /** @odoo-module **/ +import { pick } from "@web/core/utils/objects"; import options from "web_editor.snippets.options"; options.registry.facebookPage = options.Class.extend({ @@ -19,8 +20,7 @@ options.registry.facebookPage = options.Class.extend({ small_header: true, hide_cover: true, }; - this.fbData = _.defaults(_.pick(this.$target[0].dataset, _.keys(defaults)), defaults); - + this.fbData = Object.assign({}, defaults, pick(this.$target[0].dataset, Object.keys(defaults)), defaults); if (!this.fbData.href) { // Fetches the default url for facebook page from website config var self = this; diff --git a/addons/website/static/src/snippets/s_image_gallery/options.js b/addons/website/static/src/snippets/s_image_gallery/options.js index 3e3f1c90c70..411d380ca7c 100644 --- a/addons/website/static/src/snippets/s_image_gallery/options.js +++ b/addons/website/static/src/snippets/s_image_gallery/options.js @@ -312,7 +312,7 @@ options.registry.gallery = options.Class.extend({ this.mode('reset', this.getMode()); } else if (name === 'image_index_request') { var imgs = this._getImages(); - var position = _.indexOf(imgs, data.$image[0]); + var position = imgs.indexOf(data.$image[0]); imgs.splice(position, 1); switch (data.position) { case 'first': diff --git a/addons/website/static/src/snippets/s_website_form/000.js b/addons/website/static/src/snippets/s_website_form/000.js index c57842c92b1..54038169806 100644 --- a/addons/website/static/src/snippets/s_website_form/000.js +++ b/addons/website/static/src/snippets/s_website_form/000.js @@ -8,6 +8,7 @@ import publicWidget from "web.public.widget"; import dom from "web.dom"; import concurrency from "web.concurrency"; +import { sprintf } from "@web/core/utils/strings"; var _t = core._t; var qweb = core.qweb; @@ -415,9 +416,7 @@ // filter neither on required, nor on checkbox and // checking the validity of the group of checkbox is // currently done for each checkbox of that group... - var checkboxes = _.filter(inputs, function (input) { - return input.required && input.type === 'checkbox'; - }); + var checkboxes = inputs.filter(input => input.required && input.type === 'checkbox'); return !_.any(checkboxes, checkbox => checkbox.checkValidity()); // Special cases for dates and datetimes @@ -493,13 +492,13 @@ if (datetime.isValid()) { return time.datetime_to_str(datetime.toDate()); } - throw new Error(_.str.sprintf(_t("'%s' is not a correct datetime"), value)); + throw new Error(sprintf(_t("'%s' is not a correct datetime"), value)); case 'date': var date = moment(value, [date_pattern, date_pattern_wo_zero], true); if (date.isValid()) { return time.date_to_str(date.toDate()); } - throw new Error(_.str.sprintf(_t("'%s' is not a correct date"), value)); + throw new Error(sprintf(_t("'%s' is not a correct date"), value)); } return value; }, diff --git a/addons/website/static/src/snippets/s_website_form/options.js b/addons/website/static/src/snippets/s_website_form/options.js index 9b99f2d9f36..60cb4ce065d 100644 --- a/addons/website/static/src/snippets/s_website_form/options.js +++ b/addons/website/static/src/snippets/s_website_form/options.js @@ -7,6 +7,7 @@ import Dialog from "web.Dialog"; import dom from "web.dom"; import {generateHTMLId} from "web_editor.utils"; import "website.editor.snippets.options"; +import { sprintf } from "@web/core/utils/strings"; const qweb = core.qweb; const _t = core._t; @@ -307,9 +308,9 @@ options.registry.WebsiteFormEditor = FormEditor.extend({ model: 'ir.model', method: 'get_compatible_form_models', }); - + const targetModelName = this.$target[0].dataset.model_name || 'mail.mail'; - this.activeForm = _.findWhere(this.models, {model: targetModelName}); + this.activeForm = this.models.find(m => m.model === targetModelName); // Create the Form Action select this.selectActionEl = document.createElement('we-select'); this.selectActionEl.setAttribute('string', 'Action'); @@ -708,7 +709,7 @@ options.registry.WebsiteFormEditor = FormEditor.extend({ oldFormInfo = FormEditorRegistry.get(oldFormKey); } this.$target.find('.s_website_form_field').remove(); - this.activeForm = _.findWhere(this.models, {id: modelId}); + this.activeForm = this.models.find(model => model.id === modelId); } const formKey = this.activeForm.website_form_key; const formInfo = FormEditorRegistry.get(formKey); @@ -1293,7 +1294,7 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({ const list = document.createElement('we-list'); const optionText = select ? 'Option' : type === 'selection' ? 'Radio' : 'Checkbox'; list.setAttribute('string', `${optionText} List`); - list.dataset.addItemTitle = _.str.sprintf(_t("Add new %s"), optionText); + list.dataset.addItemTitle = sprintf(_t("Add new %s"), optionText); list.dataset.renderListItems = ''; list.dataset.hasDefault = ['one2many', 'many2many'].includes(type) ? 'multiple' : 'unique'; diff --git a/addons/website/static/src/systray_items/new_content.js b/addons/website/static/src/systray_items/new_content.js index 4dc9f049aa7..2a3012583a9 100644 --- a/addons/website/static/src/systray_items/new_content.js +++ b/addons/website/static/src/systray_items/new_content.js @@ -203,14 +203,14 @@ export class NewContentModal extends Component { const {id, name} = this.modulesInfo[element.moduleName]; const dialogProps = { title: element.title, - installationText: _.str.sprintf(this.newContentText.installNeeded, name), + installationText: sprintf(this.newContentText.installNeeded, name), installModule: async () => { // Update the NewContentElement with installing icon and text. this.state.newContentElements = this.state.newContentElements.map(el => { if (el.moduleXmlId === element.moduleXmlId) { el.status = MODULE_STATUS.INSTALLING; el.icon = xml``; - el.title = _.str.sprintf(this.newContentText.installPleaseWait, name); + el.title = sprintf(this.newContentText.installPleaseWait, name); } return el; }); @@ -226,7 +226,7 @@ export class NewContentModal extends Component { if (el.moduleXmlId === element.moduleXmlId) { el.status = MODULE_STATUS.FAILED_TO_INSTALL; el.icon = xml``; - el.title = _.str.sprintf(this.newContentText.failed, name); + el.title = sprintf(this.newContentText.failed, name); } return el; }); diff --git a/addons/website_blog/static/src/js/contentshare.js b/addons/website_blog/static/src/js/contentshare.js index 14b8cf6834f..86e0d4087ab 100644 --- a/addons/website_blog/static/src/js/contentshare.js +++ b/addons/website_blog/static/src/js/contentshare.js @@ -1,5 +1,6 @@ /** @odoo-module **/ +import { sprintf } from "@web/core/utils/strings"; import dom from "web.dom"; $.fn.share = function (options) { @@ -32,8 +33,8 @@ $.fn.share = function (options) { // Note: all (non-localhost) urls in a tweet have 23 characters https://support.twitter.com/articles/78124 var selectedText = this.getSelection('string').substring(0, option.maxLength - baseLength - 23); - var text = window.btoa(encodeURIComponent(_.str.sprintf(tweet, selectedText, window.location.href))); - $popover_content.append(_.str.sprintf( + var text = window.btoa(encodeURIComponent(sprintf(tweet, selectedText, window.location.href))); + $popover_content.append(sprintf( "
", option.shareLink, text, option.target)); } diff --git a/addons/website_crm_partner_assign/static/src/js/crm_partner_assign.js b/addons/website_crm_partner_assign/static/src/js/crm_partner_assign.js index ac2b6660306..db8a30319e7 100644 --- a/addons/website_crm_partner_assign/static/src/js/crm_partner_assign.js +++ b/addons/website_crm_partner_assign/static/src/js/crm_partner_assign.js @@ -1,5 +1,6 @@ /** @odoo-module **/ +import { sprintf } from "@web/core/utils/strings"; import {_t} from "web.core"; import publicWidget from "web.public.widget"; import time from "web.time"; @@ -238,7 +239,7 @@ publicWidget.registry.crmPartnerAssign = publicWidget.Widget.extend({ const contactName = ev.currentTarget.value.trim(); let titleEl = this.el.querySelector('.title'); if (!titleEl.value.trim()) { - titleEl.value = contactName ? _.str.sprintf(_t("%s's Opportunity"), contactName) : ''; + titleEl.value = contactName ? sprintf(_t("%s's Opportunity"), contactName) : ''; } }, /** diff --git a/addons/website_event_track/static/src/js/event_track_reminder.js b/addons/website_event_track/static/src/js/event_track_reminder.js index c94a7401676..c8ebf8c832f 100644 --- a/addons/website_event_track/static/src/js/event_track_reminder.js +++ b/addons/website_event_track/static/src/js/event_track_reminder.js @@ -1,5 +1,6 @@ /** @odoo-module alias=website_event_track.website_event_track_reminder **/ +import { sprintf } from "@web/core/utils/strings"; import core from "web.core"; var _t = core._t; import publicWidget from "web.public.widget"; @@ -49,7 +50,7 @@ publicWidget.registry.websiteEventTrackReminder = publicWidget.Widget.extend({ self.displayNotification({ type: 'info', title: _t('Error'), - message: _.str.sprintf(_t('Talk already in your Favorites')), + message: sprintf(_t('Talk already in your Favorites')), }); } else { self.reminderOn = reminderOnValue; diff --git a/addons/website_forum/static/src/js/website_forum.js b/addons/website_forum/static/src/js/website_forum.js index 05163635a1e..fe1c3560f1d 100644 --- a/addons/website_forum/static/src/js/website_forum.js +++ b/addons/website_forum/static/src/js/website_forum.js @@ -63,9 +63,7 @@ publicWidget.registry.websiteForum = publicWidget.Widget.extend({ maximumSelectionSize: 5, lastsearch: [], createSearchChoice: function (term) { - if (_.filter(self.lastsearch, function (s) { - return s.text.localeCompare(term) === 0; - }).length === 0) { + if (self.lastsearch.filter(s => s.text.localeCompare(term) === 0).length === 0) { //check Karma if (parseInt($('#karma').val()) >= parseInt($('#karma_edit_retag').val())) { return { diff --git a/addons/website_links/static/src/js/website_links.js b/addons/website_links/static/src/js/website_links.js index 8b83635bf32..729dcf7b94f 100644 --- a/addons/website_links/static/src/js/website_links.js +++ b/addons/website_links/static/src/js/website_links.js @@ -52,7 +52,7 @@ var SelectBox = publicWidget.Widget.extend({ if (self._objectExists(term)) { return null; } - return {id: term, text: _.str.sprintf("Create '%s'", term)}; + return { id: term, text: `Create '${term}'` }; }, createSearchChoicePosition: 'bottom', multiple: false, diff --git a/addons/website_payment/static/src/js/website_payment_form.js b/addons/website_payment/static/src/js/website_payment_form.js index 599fe99e896..b13ba8d5245 100644 --- a/addons/website_payment/static/src/js/website_payment_form.js +++ b/addons/website_payment/static/src/js/website_payment_form.js @@ -2,6 +2,7 @@ import core, { _t } from 'web.core'; import checkoutForm from 'payment.checkout_form'; +import { sprintf } from '@web/core/utils/strings'; checkoutForm.include({ events: Object.assign({}, checkoutForm.prototype.events || {}, { @@ -45,7 +46,7 @@ checkoutForm.include({ const $field = this.$('input[name="' + id + '"],select[name="' + id + '"]'); $field.removeClass('is-invalid').popover('dispose'); if (!$field.val().trim()) { - errorFields[id] = _.str.sprintf(_t("Field '%s' is mandatory"), mandatoryFields[id]); + errorFields[id] = sprintf(_t("Field '%s' is mandatory"), mandatoryFields[id]); } } if (Object.keys(errorFields).length) { diff --git a/addons/website_payment/static/src/snippets/s_donation/000.js b/addons/website_payment/static/src/snippets/s_donation/000.js index d5fd49c9da9..44069ac9e25 100644 --- a/addons/website_payment/static/src/snippets/s_donation/000.js +++ b/addons/website_payment/static/src/snippets/s_donation/000.js @@ -1,5 +1,6 @@ /** @odoo-module **/ +import { sprintf } from '@web/core/utils/strings'; import {_t} from 'web.core'; import publicWidget from 'web.public.widget'; @@ -126,7 +127,7 @@ publicWidget.registry.DonationSnippet = publicWidget.Widget.extend({ } else if (amount < parseFloat(minAmount)) { const before = this.currency.position === "before" ? this.currency.symbol : ""; const after = this.currency.position === "after" ? this.currency.symbol : ""; - errorMessage = _.str.sprintf(_t("The minimum donation amount is %s%s%s"), before, minAmount, after); + errorMessage = sprintf(_t("The minimum donation amount is %s%s%s"), before, minAmount, after); } if (errorMessage) { $(ev.currentTarget).before($('

', { diff --git a/addons/website_sale_product_configurator/static/tests/tours/website_sale_buy.js b/addons/website_sale_product_configurator/static/tests/tours/website_sale_buy.js index a61704c069f..7b82bbed114 100644 --- a/addons/website_sale_product_configurator/static/tests/tours/website_sale_buy.js +++ b/addons/website_sale_product_configurator/static/tests/tours/website_sale_buy.js @@ -6,9 +6,7 @@ import { registry } from "@web/core/registry"; import "website_sale.tour"; -var addCartStepIndex = _.findIndex(registry.category("web_tour.tours").get("shop_buy_product").steps, function (step) { - return (step.id === 'add_cart_step'); -}); +var addCartStepIndex = registry.category("web_tour.tours").get("shop_buy_product").steps.findIndex(step => step.id === 'add_cart_step'); registry.category("web_tour.tours").get("shop_buy_product").steps.splice(addCartStepIndex + 1, 1, { content: "click in modal on 'Proceed to checkout' button", diff --git a/addons/website_slides/static/src/js/portal_chatter.js b/addons/website_slides/static/src/js/portal_chatter.js index 4a81d795d01..668e456ca5d 100644 --- a/addons/website_slides/static/src/js/portal_chatter.js +++ b/addons/website_slides/static/src/js/portal_chatter.js @@ -2,6 +2,7 @@ import { _t } from 'web.core'; import { PortalChatter } from 'portal.chatter'; +import { sprintf } from '@web/core/utils/strings'; /** * PortalChatter @@ -18,7 +19,7 @@ PortalChatter.include({ _reloadChatterContent: async function (data) { await this._super(...arguments); if (this.options.res_model === "slide.channel") { - $('#review-tab').text(_.str.sprintf(_t('Reviews (%d)'), data.rating_count)); + $('#review-tab').text(sprintf(_t('Reviews (%d)'), data.rating_count)); } }, }); diff --git a/addons/website_slides/static/src/js/slides_course_fullscreen_player.js b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js index ea6eb50ebdf..e1bfcf25abb 100644 --- a/addons/website_slides/static/src/js/slides_course_fullscreen_player.js +++ b/addons/website_slides/static/src/js/slides_course_fullscreen_player.js @@ -288,7 +288,7 @@ */ _getCurrentIndex: function () { var slide = this.get('slideEntry'); - var currentIndex = _.findIndex(this.slideEntries, function (entry) { + var currentIndex = this.slideEntries.findIndex(entry =>{ return entry.id === slide.id && entry.isQuiz === slide.isQuiz; }); return currentIndex; @@ -609,11 +609,11 @@ } else if (slideData.category === 'video' && slideData.videoSourceType === 'vimeo') { slideData.embedCode = Markup(slideData.embedCode); } else if (slideData.category === 'infographic') { - slideData.embedUrl = _.str.sprintf('/web/image/slide.slide/%s/image_1024', slideData.id); + slideData.embedUrl = `/web/image/slide.slide/${slideData.id}/image_1024`; } else if (slideData.category === 'document') { slideData.embedUrl = $(slideData.embedCode).attr('src'); } - // fill empty property to allow searching on it with _.filter(list, matcher) + // fill empty property to allow searching on it with list.filter(matcher) slideData.isQuiz = !!slideData.isQuiz; slideData.hasQuestion = !!slideData.hasQuestion; // technical settings for the Fullscreen to work @@ -644,7 +644,7 @@ if (this.get('slide').isQuiz){ params.quiz = 1; } - var fullscreenUrl = _.str.sprintf('%s?%s', url, $.param(params)); + var fullscreenUrl = `${url}?${$.param(params)}`; history.pushState(null, '', fullscreenUrl); }, /** @@ -755,8 +755,7 @@ _toggleSlideCompleted: async function (slide, completed = true) { await this._super(...arguments); - const slideMatch = _.matcher({id: slide.id}); - const fsSlides = _.filter(this.slides, slideMatch); + const fsSlides = this.slides.filter(_slide => _slide.id === slide.id); fsSlides.forEach(slide => slide.completed = completed); diff --git a/addons/website_slides/static/src/js/slides_course_tag_add.js b/addons/website_slides/static/src/js/slides_course_tag_add.js index 29d69576db8..833c0a0cd7d 100644 --- a/addons/website_slides/static/src/js/slides_course_tag_add.js +++ b/addons/website_slides/static/src/js/slides_course_tag_add.js @@ -1,5 +1,6 @@ /** @odoo-module **/ +import { sprintf } from '@web/core/utils/strings'; import { _t } from 'web.core'; import Dialog from 'web.Dialog'; import publicWidget from 'web.public.widget'; @@ -111,7 +112,7 @@ var TagCourseDialog = Dialog.extend({ }, createSearchChoice: function (term, data) { var addedTags = $(this.opts.element).select2('data'); - if (_.filter(_.union(addedTags, data), function (tag) { + if (addedTags.concat(data).filter(tag => { return tag.text.toLowerCase().localeCompare(term.toLowerCase()) === 0; }).length === 0) { if (this.opts.can_create) { @@ -119,7 +120,7 @@ var TagCourseDialog = Dialog.extend({ id: _.uniqueId('tag_'), create: true, tag: term, - text: _.str.sprintf(_t("Create new %s '%s'"), tag, term), + text: sprintf(_t("Create new %s '%s'"), tag, term), }; } else { return undefined; diff --git a/addons/website_slides/static/src/js/slides_upload.js b/addons/website_slides/static/src/js/slides_upload.js index 7ff32dc4680..8aa27033705 100644 --- a/addons/website_slides/static/src/js/slides_upload.js +++ b/addons/website_slides/static/src/js/slides_upload.js @@ -1,5 +1,6 @@ /** @odoo-module **/ +import { sprintf } from '@web/core/utils/strings'; import {_t, qweb as QWeb} from 'web.core'; import Dialog from 'web.Dialog'; import publicWidget from 'web.public.widget'; @@ -293,7 +294,7 @@ var SlideUploadDialog = Dialog.extend({ }, createSearchChoice: function (term, data) { var addedTags = $(this.opts.element).select2('data'); - if (_.filter(_.union(addedTags, data), function (tag) { + if (addedTags.concat(data).filter(tag => { return tag.text.toLowerCase().localeCompare(term.toLowerCase()) === 0; }).length === 0) { if (this.opts.can_create) { @@ -301,7 +302,7 @@ var SlideUploadDialog = Dialog.extend({ id: _.uniqueId('tag_'), create: true, tag: term, - text: _.str.sprintf(_t("Create new %s '%s'"), tag, term), + text: sprintf(_t("Create new %s '%s'"), tag, term), }; } else { return undefined; @@ -650,23 +651,23 @@ var SlideUploadDialog = Dialog.extend({ this.set('state', '_import'); if (this.modulesToInstallStatus.installing) { this.$('#o_wslides_install_module_text') - .text(_.str.sprintf(_t('Already installing "%s".'), this.modulesToInstallStatus.name)); + .text(sprintf(_t('Already installing "%s".'), this.modulesToInstallStatus.name)); } else if (this.modulesToInstallStatus.failed) { this.$('#o_wslides_install_module_text') - .text(_.str.sprintf(_t('Failed to install "%s".'), this.modulesToInstallStatus.name)); + .text(sprintf(_t('Failed to install "%s".'), this.modulesToInstallStatus.name)); } } else { this.modulesToInstallStatus = Object.assign({}, this.modulesToInstall.find( function (item) { return item.id === moduleId; })); this.set('state', '_import'); this.$('#o_wslides_install_module_text') - .text(_.str.sprintf(_t('Do you want to install the "%s" app?'), this.modulesToInstallStatus.name)); + .text(sprintf(_t('Do you want to install the "%s" app?'), this.modulesToInstallStatus.name)); } }, _onClickInstallModuleConfirm: function () { var self = this; var $el = this.$('#o_wslides_install_module_text'); - $el.text(_.str.sprintf(_t('Installing "%s".'), this.modulesToInstallStatus.name)); + $el.text(sprintf(_t('Installing "%s".'), this.modulesToInstallStatus.name)); this.modulesToInstallStatus.installing = true; this._resetModalButton(); this._rpc({ @@ -681,7 +682,7 @@ var SlideUploadDialog = Dialog.extend({ } window.location.href = redirectUrl; }, function () { - $el.text(_.str.sprintf(_t('Failed to install "%s".'), self.modulesToInstallStatus.name)); + $el.text(sprintf(_t('Failed to install "%s".'), self.modulesToInstallStatus.name)); self.modulesToInstallStatus.installing = false; self.modulesToInstallStatus.failed = true; self._resetModalButton();