diff --git a/addons/test_website/static/tests/tours/custom_snippets.js b/addons/test_website/static/tests/tours/custom_snippets.js
index 1a770c46fcb..51d59135a71 100644
--- a/addons/test_website/static/tests/tours/custom_snippets.js
+++ b/addons/test_website/static/tests/tours/custom_snippets.js
@@ -42,7 +42,7 @@ wTourUtils.registerWebsitePreviewTour('test_custom_snippet', {
},
{
content: "confirm reload",
- trigger: ".modal-dialog button span:contains('Save and Reload')",
+ trigger: ".modal-dialog button:contains('Save and Reload')",
},
{
content: "ensure custom snippet appeared",
@@ -87,7 +87,7 @@ wTourUtils.registerWebsitePreviewTour('test_custom_snippet', {
},
{
content: "confirm delete",
- trigger: ".modal-dialog button:has(span:contains('Yes'))",
+ trigger: ".modal-dialog button:contains('Yes')",
},
{
content: "ensure custom snippet disappeared",
diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js
index 046c69f3e09..9fddb11cff3 100644
--- a/addons/web_editor/static/src/js/editor/snippets.editor.js
+++ b/addons/web_editor/static/src/js/editor/snippets.editor.js
@@ -2,7 +2,7 @@
import { Mutex } from "@web/core/utils/concurrency";
import { clamp } from "@web/core/utils/numbers";
-import Dialog from "@web/legacy/js/core/dialog";
+import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import dom from "@web/legacy/js/core/dom";
import Widget from "@web/legacy/js/core/widget";
import { useDragAndDrop } from "@web_editor/js/editor/drag_and_drop";
@@ -1826,6 +1826,7 @@ var SnippetsMenu = Widget.extend({
this.orm = this.bindService("orm");
this.notification = this.bindService("notification");
+ this.dialog = this.bindService("dialog");
},
/**
* @override
@@ -3923,54 +3924,38 @@ var SnippetsMenu = Widget.extend({
* @param {Event} ev
*/
_onInstallBtnClick: function (ev) {
- var self = this;
var $snippet = $(ev.currentTarget).closest('[data-module-id]');
var moduleID = $snippet.data('moduleId');
var name = $snippet.attr('name');
- new Dialog(this, {
+ const bodyText = _t("Do you want to install %s App?", name);
+ const linkText = _t("More info about this app.");
+ const linkUrl = '/web#id=' + encodeURIComponent(moduleID) + '&view_type=form&model=ir.module.module&action=base.open_module_tree';
+ this.dialog.add(ConfirmationDialog, {
title: _t("Install %s", name),
- size: 'medium',
- $content: $('
', {text: _t("Do you want to install the %s App?", name)}).append(
- $(' ', {
- target: '_blank',
- href: '/web#id=' + encodeURIComponent(moduleID) + '&view_type=form&model=ir.module.module&action=base.open_module_tree',
- text: _t("More info about this app."),
- class: 'ml4',
- })
- ),
- buttons: [{
- text: _t("Save and Install"),
- classes: 'btn-primary',
- click: function () {
- this.$footer.find('.btn').toggleClass('o_hidden');
- this.orm.call("ir.module.module", "button_immediate_install", [[moduleID]]).then(() => {
- self.trigger_up('request_save', {
- invalidateSnippetCache: true,
- _toMutex: true,
- reloadWebClient: true,
- });
- }).catch(reason => {
- if (reason instanceof RPCError) {
- this.close();
- const message = markup(_t("Could not install module %s ", escape(name)));
- self.notification.add(message, {
- type: 'danger',
- sticky: true,
- });
- } else {
- return Promise.reject(reason);
- }
+ body: markup(`${escape(bodyText)}\n${escape(linkText)} `),
+ confirm: async () => {
+ try {
+ await this.orm.call("ir.module.module", "button_immediate_install", [[moduleID]]);
+ this.trigger_up('request_save', {
+ invalidateSnippetCache: true,
+ _toMutex: true,
+ reloadWebClient: true,
});
- },
- }, {
- text: _t("Install in progress"),
- icon: 'fa-spin fa-circle-o-notch fa-spin mr8',
- classes: 'btn-primary disabled o_hidden',
- }, {
- text: _t("Cancel"),
- close: true,
- }],
- }).open();
+ } catch (e) {
+ if (e instanceof RPCError) {
+ const message = escape(_t("Could not install module %s", name));
+ this.notification.add(message, {
+ type: "danger",
+ sticky: true,
+ });
+ } else {
+ throw e;
+ }
+ }
+ },
+ confirmLabel: _t("Save and Install"),
+ cancel: () => {},
+ });
},
/**
* @private
@@ -4012,26 +3997,20 @@ var SnippetsMenu = Widget.extend({
const $snippet = $(ev.target).closest('.oe_snippet');
const snippetId = parseInt(ev.currentTarget.dataset.snippetId);
ev.stopPropagation();
- new Dialog(this, {
- size: 'medium',
- title: _t('Confirmation'),
- $content: $('' + _t("Are you sure you want to delete the snippet: %s?", $snippet.attr('name')) + '
'),
- buttons: [{
- text: _t("Yes"),
- close: true,
- classes: 'btn-primary',
- click: async () => {
- await this.orm.call("ir.ui.view", "delete_snippet", [], {
- 'view_id': snippetId,
- 'template_key': this.options.snippets,
- });
- await this._loadSnippetsTemplates(true);
- },
- }, {
- text: _t("No"),
- close: true,
- }],
- }).open();
+ const message = _t("Are you sure you want to delete the snippet %s?", $snippet[0].getAttribute("name"));
+ this.dialog.add(ConfirmationDialog, {
+ body: message,
+ confirm: async () => {
+ await this.orm.call("ir.ui.view", "delete_snippet", [], {
+ 'view_id': snippetId,
+ 'template_key': this.options.snippets,
+ });
+ await this._loadSnippetsTemplates(true);
+ },
+ cancel: () => null,
+ confirmLabel: _t("Yes"),
+ cancelLabel: _t("No"),
+ });
},
/**
* @private
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 2807f3db9bb..394ed1e57f2 100644
--- a/addons/web_editor/static/src/js/editor/snippets.options.js
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -2,7 +2,7 @@
import { attachComponent } from "@web/legacy/utils";
import { MediaDialog } from "@web_editor/components/media_dialog/media_dialog";
-import Dialog from "@web/legacy/js/core/dialog";
+import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import dom from "@web/legacy/js/core/dom";
import { throttleForAnimation, debounce } from "@web/core/utils/timing";
import { clamp } from "@web/core/utils/numbers";
@@ -3228,6 +3228,8 @@ const SnippetOptionWidget = Widget.extend({
this._userValueWidgets = [];
this._actionQueues = new Map();
+
+ this.dialog = this.bindService("dialog");
},
/**
* @override
@@ -4303,9 +4305,10 @@ const SnippetOptionWidget = Widget.extend({
const warnMessage = await this._checkIfWidgetsUpdateNeedWarning(widgets);
if (warnMessage) {
const okWarning = await new Promise(resolve => {
- Dialog.confirm(this, warnMessage, {
- confirm_callback: () => resolve(true),
- cancel_callback: () => resolve(false),
+ this.dialog.add(ConfirmationDialog, {
+ body: warnMessage,
+ confirm: () => resolve(true),
+ cancel: () => resolve(false),
});
});
if (!okWarning) {
@@ -8750,65 +8753,56 @@ registry.SnippetSave = SnippetOptionWidget.extend({
*/
saveSnippet: function (previewMode, widgetValue, params) {
return new Promise(resolve => {
- Dialog.confirm(this, _t("To save a snippet, we need to save all your previous modifications and reload the page."), {
- cancel_callback: () => resolve(false),
- buttons: [
- {
- text: _t("Save and Reload"),
- classes: 'btn-primary',
- close: true,
- click: () => {
- const isButton = this.$target[0].matches("a.btn");
- const snippetKey = !isButton ? this.$target[0].dataset.snippet : "s_button";
- let thumbnailURL;
- this.trigger_up('snippet_thumbnail_url_request', {
- key: snippetKey,
- onSuccess: url => thumbnailURL = url,
- });
- let context;
- this.trigger_up('context_get', {
- callback: ctx => context = ctx,
- });
- this.trigger_up('request_save', {
- reloadEditor: true,
- invalidateSnippetCache: true,
- onSuccess: async () => {
- const defaultSnippetName = !isButton
- ? _t("Custom %s", this.data.snippetName)
- : _t("Custom Button");
- const targetCopyEl = this.$target[0].cloneNode(true);
- delete targetCopyEl.dataset.name;
- if (isButton) {
- targetCopyEl.classList.remove("mb-2");
- targetCopyEl.classList.add("o_snippet_drop_in_only", "s_custom_button");
- }
- // By the time onSuccess is called after request_save, the
- // current widget has been destroyed and is orphaned, so this._rpc
- // will not work as it can't trigger_up. For this reason, we need
- // to bypass the service provider and use the global RPC directly
- await jsonrpc(`/web/dataset/call_kw/ir.ui.view/save_snippet`, {
- model: "ir.ui.view",
- method: "save_snippet",
- args: [],
- kwargs: {
- 'name': defaultSnippetName,
- 'arch': targetCopyEl.outerHTML,
- 'template_key': this.options.snippets,
- 'snippet_key': snippetKey,
- 'thumbnail_url': thumbnailURL,
- 'context': context,
- },
- });
+ this.dialog.add(ConfirmationDialog, {
+ body: _t("To save a snippet, we need to save all your previous modifications and reload the page."),
+ cancel: () => resolve(false),
+ confirmLabel: _t("Save and Reload"),
+ confirm: () => {
+ const isButton = this.$target[0].matches("a.btn");
+ const snippetKey = !isButton ? this.$target[0].dataset.snippet : "s_button";
+ let thumbnailURL;
+ this.trigger_up('snippet_thumbnail_url_request', {
+ key: snippetKey,
+ onSuccess: url => thumbnailURL = url,
+ });
+ let context;
+ this.trigger_up('context_get', {
+ callback: ctx => context = ctx,
+ });
+ this.trigger_up('request_save', {
+ reloadEditor: true,
+ invalidateSnippetCache: true,
+ onSuccess: async () => {
+ const defaultSnippetName = !isButton
+ ? _t("Custom %s", this.data.snippetName)
+ : _t("Custom Button");
+ const targetCopyEl = this.$target[0].cloneNode(true);
+ delete targetCopyEl.dataset.name;
+ if (isButton) {
+ targetCopyEl.classList.remove("mb-2");
+ targetCopyEl.classList.add("o_snippet_drop_in_only", "s_custom_button");
+ }
+ // By the time onSuccess is called after request_save, the
+ // current widget has been destroyed and is orphaned, so this._rpc
+ // will not work as it can't trigger_up. For this reason, we need
+ // to bypass the service provider and use the global RPC directly
+ await jsonrpc(`/web/dataset/call_kw/ir.ui.view/save_snippet`, {
+ model: "ir.ui.view",
+ method: "save_snippet",
+ args: [],
+ kwargs: {
+ 'name': defaultSnippetName,
+ 'arch': targetCopyEl.outerHTML,
+ 'template_key': this.options.snippets,
+ 'snippet_key': snippetKey,
+ 'thumbnail_url': thumbnailURL,
+ 'context': context,
},
});
- resolve(true);
- }
- }, {
- text: _t("Cancel"),
- close: true,
- click: () => resolve(false),
- }
- ]
+ },
+ });
+ resolve(true);
+ },
});
});
},
diff --git a/addons/website/static/src/js/editor/snippets.editor.js b/addons/website/static/src/js/editor/snippets.editor.js
index da2c92a1feb..7694bfc3aa1 100644
--- a/addons/website/static/src/js/editor/snippets.editor.js
+++ b/addons/website/static/src/js/editor/snippets.editor.js
@@ -1,12 +1,13 @@
/** @odoo-modules **/
import { _t } from "@web/core/l10n/translation";
-import Dialog from "@web/legacy/js/core/dialog";
+import { Dialog } from "@web/core/dialog/dialog";
+import { useChildRef } from "@web/core/utils/hooks";
import weSnippetEditor from "@web_editor/js/editor/snippets.editor";
import wSnippetOptions from "@website/js/editor/snippets.options";
import wUtils from "@website/js/utils";
import * as OdooEditorLib from "@web_editor/js/editor/odoo-editor/src/utils/utils";
-import { renderToElement } from "@web/core/utils/render";
+import { Component, onMounted, useRef, useState } from "@odoo/owl";
import { throttleForAnimation } from "@web/core/utils/timing";
import {
applyTextHighlight,
@@ -14,7 +15,6 @@ import {
switchTextHighlight,
getCurrentTextHighlight
} from "@website/js/text_processing";
-
const getDeepRange = OdooEditorLib.getDeepRange;
const getTraversedNodes = OdooEditorLib.getTraversedNodes;
@@ -52,6 +52,7 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
init() {
this._super(...arguments);
this.notification = this.bindService("notification");
+ this.dialog = this.bindService("dialog");
},
/**
* @override
@@ -192,43 +193,53 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({
$apiKeyHelp.empty().text(message);
}
- const $content = $(renderToElement('website.s_google_map_modal', {
- apiKey: apiKey,
- }));
- if (!apiKeyValidation.isValid && apiKeyValidation.message) {
- applyError.call($content, apiKeyValidation.message);
- }
+ const GoogleMapAPIKeyDialog = class extends Component {
+ static template = "website.s_google_map_modal";
+ static components = { Dialog };
+ static props = {
+ onMounted: Function,
+ close: Function,
+ confirm: Function
+ };
+ setup() {
+ this.modalRef = useChildRef();
+ this.state = useState({ apiKey: apiKey });
+ this.apiKeyInput = useRef("apiKeyInput");
+ onMounted(() => this.props.onMounted(this.modalRef));
+ }
+ onClickSave() {
+ this.props.confirm(this.modalRef, this.state.apiKey);
+ }
+ };
return new Promise(resolve => {
let invalidated = false;
- const dialog = new Dialog(this, {
- size: 'medium',
- title: _t("Google Map API Key"),
- buttons: [
- {text: _t("Save"), classes: 'btn-primary', click: async (ev) => {
- const valueAPIKey = dialog.$('#api_key_input').val();
- if (!valueAPIKey) {
- applyError.call(dialog.$el, _t("Enter an API Key"));
- return;
- }
- const $button = $(ev.currentTarget);
- $button.prop('disabled', true);
- const res = await this._validateGMapAPIKey(valueAPIKey);
- if (res.isValid) {
- await this.orm.write("website", [websiteId], { google_maps_api_key: valueAPIKey });
- invalidated = true;
- dialog.close();
- } else {
- applyError.call(dialog.$el, res.message);
- }
- $button.prop("disabled", false);
- }},
- {text: _t("Cancel"), close: true}
- ],
- $content: $content,
+ this.dialog.add(GoogleMapAPIKeyDialog, {
+ onMounted: (modalRef) => {
+ if (!apiKeyValidation.isValid && apiKeyValidation.message) {
+ applyError.call($(modalRef.el), apiKeyValidation.message);
+ }
+ },
+ confirm: async (modalRef, valueAPIKey) => {
+ if (!valueAPIKey) {
+ applyError.call($(modalRef.el), _t("Enter an API Key"));
+ return;
+ }
+ const $button = $(modalRef.el).find("button");
+ $button.prop('disabled', true);
+ const res = await this._validateGMapAPIKey(valueAPIKey);
+ if (res.isValid) {
+ await this.orm.write("website", [websiteId], {google_maps_api_key: valueAPIKey});
+ invalidated = true;
+ return true;
+ } else {
+ applyError.call($(modalRef.el), res.message);
+ }
+ $button.prop("disabled", false);
+ }
+ }, {
+ onClose: () => resolve(invalidated),
});
- dialog.on('closed', this, () => resolve(invalidated));
- dialog.open();
});
},
/**
diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js
index a580bdf7d23..42f632aadf2 100644
--- a/addons/website/static/src/js/editor/snippets.options.js
+++ b/addons/website/static/src/js/editor/snippets.options.js
@@ -1,7 +1,9 @@
/** @odoo-module **/
import { loadCSS } from "@web/core/assets";
-import Dialog from "@web/legacy/js/core/dialog";
+import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
+import { Dialog } from "@web/core/dialog/dialog";
+import { useChildRef } from "@web/core/utils/hooks";
import weUtils from "@web_editor/js/common/utils";
import options from "@web_editor/js/editor/snippets.options";
import { NavbarLinkPopoverWidget } from "@website/js/widgets/link_popover_widget";
@@ -26,7 +28,7 @@ import {
drawTextHighlightSVG,
} from "@website/js/text_processing";
-import { markup } from "@odoo/owl";
+import { Component, markup, useRef, useState } from "@odoo/owl";
const InputUserValueWidget = options.userValueWidgetsRegistry['we-input'];
const SelectUserValueWidget = options.userValueWidgetsRegistry['we-select'];
@@ -147,6 +149,13 @@ const FontFamilyPickerUserValueWidget = SelectUserValueWidget.extend({
}),
fontVariables: [], // Filled by editor menu when all options are loaded
+ /**
+ * @override
+ */
+ init() {
+ this.dialog = this.bindService("dialog");
+ return this._super(...arguments);
+ },
/**
* @override
*/
@@ -272,81 +281,92 @@ const FontFamilyPickerUserValueWidget = SelectUserValueWidget.extend({
* @private
*/
_onAddGoogleFontClick: function (ev) {
+ const addGoogleFontDialog = class extends Component {
+ static template = "website.dialog.addGoogleFont";
+ static components = { Dialog };
+ static props = { close: Function, title: String, onClickSave: Function };
+ title = _t("Add a Google Font");
+ state = useState({ valid: true, loading: false, googleServe: true });
+ fontInput = useRef("fontInput");
+ async onClickSave() {
+ if (this.state.loading) {
+ return;
+ }
+ this.state.loading = true;
+ const shouldClose = await this.props.onClickSave(this.state, this.fontInput.el);
+ if (shouldClose) {
+ this.props.close();
+ return;
+ }
+ this.state.loading = false;
+ }
+ onClickCancel() {
+ this.props.close();
+ }
+ };
const variable = $(ev.currentTarget).data('variable');
- const dialog = new Dialog(this, {
+ this.dialog.add(addGoogleFontDialog, {
title: _t("Add a Google Font"),
- $content: $(renderToElement('website.dialog.addGoogleFont')),
- buttons: [
- {
- text: _t("Save & Reload"),
- classes: 'btn-primary',
- click: async () => {
- const inputEl = dialog.el.querySelector('.o_input_google_font');
- // if font page link (what is expected)
- let m = inputEl.value.match(/\bspecimen\/([\w+]+)/);
- if (!m) {
- // if embed code (so that it works anyway if the user put the embed code instead of the page link)
- m = inputEl.value.match(/\bfamily=([\w+]+)/);
- if (!m) {
- inputEl.classList.add('is-invalid');
- return;
- }
- }
+ onClickSave: async (state, inputEl) => {
+ // if font page link (what is expected)
+ let m = inputEl.value.match(/\bspecimen\/([\w+]+)/);
+ if (!m) {
+ // if embed code (so that it works anyway if the user put the embed code instead of the page link)
+ m = inputEl.value.match(/\bfamily=([\w+]+)/);
+ if (!m) {
+ inputEl.classList.add('is-invalid');
+ return;
+ }
+ }
- let isValidFamily = false;
+ let isValidFamily = false;
- try {
- // Font family is an encoded query parameter:
- // "Open+Sans" needs to remain "Open+Sans".
- const result = await fetch("https://fonts.googleapis.com/css?family=" + m[1] + ':300,300i,400,400i,700,700i', {method: 'HEAD'});
- // Google fonts server returns a 400 status code if family is not valid.
- if (result.ok) {
- isValidFamily = true;
- }
- } catch (error) {
- console.error(error);
- }
+ try {
+ // Font family is an encoded query parameter:
+ // "Open+Sans" needs to remain "Open+Sans".
+ const result = await fetch("https://fonts.googleapis.com/css?family=" + m[1] + ':300,300i,400,400i,700,700i', {method: 'HEAD'});
+ // Google fonts server returns a 400 status code if family is not valid.
+ if (result.ok) {
+ isValidFamily = true;
+ }
+ } catch (error) {
+ console.error(error);
+ }
- if (!isValidFamily) {
- inputEl.classList.add('is-invalid');
- return;
- }
+ if (!isValidFamily) {
+ inputEl.classList.add('is-invalid');
+ return;
+ }
- const font = m[1].replace(/\+/g, ' ');
- const googleFontServe = dialog.el.querySelector('#google_font_serve').checked;
- const fontName = `'${font}'`;
- // If the font already exists, it will only be added if
- // the user chooses to add it locally when it is already
- // imported from the Google Fonts server.
- const fontExistsLocally = this.googleLocalFonts.some(localFont => localFont.split(':')[0] === fontName);
- const fontExistsOnServer = this.allFonts.includes(fontName);
- const preventFontAddition = fontExistsLocally || (fontExistsOnServer && googleFontServe);
- if (preventFontAddition) {
- inputEl.classList.add('is-invalid');
- // Show custom validity error message.
- inputEl.setCustomValidity(_t("This font already exists, you can only add it as a local font to replace the server version."));
- inputEl.reportValidity();
- return;
- }
- if (googleFontServe) {
- this.googleFonts.push(font);
- } else {
- this.googleLocalFonts.push(`'${font}': ''`);
- }
- this.trigger_up('google_fonts_custo_request', {
- values: {[variable]: `'${font}'`},
- googleFonts: this.googleFonts,
- googleLocalFonts: this.googleLocalFonts,
- });
- },
- },
- {
- text: _t("Discard"),
- close: true,
- },
- ],
+ const font = m[1].replace(/\+/g, ' ');
+ const googleFontServe = state.googleServe;
+ const fontName = `'${font}'`;
+ // If the font already exists, it will only be added if
+ // the user chooses to add it locally when it is already
+ // imported from the Google Fonts server.
+ const fontExistsLocally = this.googleLocalFonts.some(localFont => localFont.split(':')[0] === fontName);
+ const fontExistsOnServer = this.allFonts.includes(fontName);
+ const preventFontAddition = fontExistsLocally || (fontExistsOnServer && googleFontServe);
+ if (preventFontAddition) {
+ inputEl.classList.add('is-invalid');
+ // Show custom validity error message.
+ inputEl.setCustomValidity(_t("This font already exists, you can only add it as a local font to replace the server version."));
+ inputEl.reportValidity();
+ return;
+ }
+ if (googleFontServe) {
+ this.googleFonts.push(font);
+ } else {
+ this.googleLocalFonts.push(`'${font}': ''`);
+ }
+ this.trigger_up('google_fonts_custo_request', {
+ values: {[variable]: `'${font}'`},
+ googleFonts: this.googleFonts,
+ googleLocalFonts: this.googleLocalFonts,
+ });
+ return true;
+ },
});
- dialog.open();
},
/**
* @private
@@ -357,9 +377,10 @@ const FontFamilyPickerUserValueWidget = SelectUserValueWidget.extend({
const values = {};
const save = await new Promise(resolve => {
- Dialog.confirm(this, _t("Deleting a font requires a reload of the page. This will save all your changes and reload the page, are you sure you want to proceed?"), {
- confirm_callback: () => resolve(true),
- cancel_callback: () => resolve(false),
+ this.dialog.add(ConfirmationDialog, {
+ body: _t("Deleting a font requires a reload of the page. This will save all your changes and reload the page, are you sure you want to proceed?"),
+ confirm: () => resolve(true),
+ cancel: () => resolve(false),
});
});
if (!save) {
@@ -1399,9 +1420,10 @@ options.registry.OptionsTab = options.Class.extend({
*/
async switchTheme(previewMode, widgetValue, params) {
const save = await new Promise(resolve => {
- Dialog.confirm(this, _t("Changing theme requires to leave the editor. This will save all your changes, are you sure you want to proceed? Be careful that changing the theme will reset all your color customizations."), {
- confirm_callback: () => resolve(true),
- cancel_callback: () => resolve(false),
+ this.dialog.add(ConfirmationDialog, {
+ body: _t("Changing theme requires to leave the editor. This will save all your changes, are you sure you want to proceed? Be careful that changing the theme will reset all your color customizations."),
+ confirm: () => resolve(true),
+ cancel: () => resolve(false),
});
});
if (!save) {
@@ -1420,14 +1442,11 @@ options.registry.OptionsTab = options.Class.extend({
// the dialog box 'action_view_base_language_install'
const websiteId = this.options.context.website_id;
const save = await new Promise((resolve) => {
- Dialog.confirm(
- this,
- _t("Adding a language requires to leave the editor. This will save all your changes, are you sure you want to proceed?"),
- {
- confirm_callback: () => resolve(true),
- cancel_callback: () => resolve(false),
- }
- );
+ this.dialog.add(ConfirmationDialog, {
+ body: _t("Adding a language requires to leave the editor. This will save all your changes, are you sure you want to proceed?"),
+ confirm: () => resolve(true),
+ cancel: () => resolve(false),
+ });
});
if (!save) {
return;
@@ -2691,51 +2710,54 @@ options.registry.anchor = options.Class.extend({
* @param {Element} buttonEl
*/
_openAnchorDialog(buttonEl) {
- var self = this;
- var buttons = [{
- text: _t("Save & copy"),
- classes: 'btn-primary',
- click: function () {
- var $input = this.$('.o_input_anchor_name');
- var anchorName = self._text2Anchor($input.val());
- if (self.$target[0].id === anchorName) {
+ const anchorDialog = class extends Component {
+ static template = "website.dialog.anchorName";
+ static props = { close: Function, confirm: Function, delete: Function, currentAnchor: String };
+ static components = { Dialog };
+ title = _t("Link Anchor");
+ modalRef = useChildRef();
+ onClickConfirm() {
+ const shouldClose = this.props.confirm(this.modalRef);
+ if (shouldClose) {
+ this.props.close();
+ }
+ }
+ onClickDelete() {
+ this.props.delete();
+ this.props.close();
+ }
+ onClickDiscard() {
+ this.props.close();
+ }
+ };
+ const props = {
+ confirm: (modalRef) => {
+ const inputEl = modalRef.el.querySelector(".o_input_anchor_name");
+ const anchorName = this._text2Anchor(inputEl.value);
+ if (this.$target[0].id === anchorName) {
// If the chosen anchor name is already the one used by the
// element, close the dialog and do nothing else
this.close();
return;
}
- const alreadyExists = !!document.getElementById(anchorName);
- this.$('.o_anchor_already_exists').toggleClass('d-none', !alreadyExists);
- $input.toggleClass('is-invalid', alreadyExists);
+ const alreadyExists = !!this.$target[0].ownerDocument.getElementById(anchorName);
+ modalRef.el.querySelector('.o_anchor_already_exists').classList.toggle('d-none', !alreadyExists);
+ inputEl.classList.toggle('is-invalid', alreadyExists);
if (!alreadyExists) {
- self._setAnchorName(anchorName);
- this.close();
+ this._setAnchorName(anchorName);
buttonEl.click();
+ return true;
}
},
- }, {
- text: _t("Discard"),
- close: true,
- }];
+ currentAnchor: decodeURIComponent(this.$target.attr('id')),
+ };
if (this.$target.attr('id')) {
- buttons.push({
- text: _t("Remove"),
- classes: 'btn-link ms-auto',
- icon: 'fa-trash',
- close: true,
- click: function () {
- self._setAnchorName();
- },
- });
+ props["delete"] = () => {
+ this._setAnchorName();
+ };
}
- new Dialog(this, {
- title: _t("Link Anchor"),
- $content: $(renderToElement('website.dialog.anchorName', {
- currentAnchor: decodeURIComponent(this.$target.attr('id')),
- })),
- buttons: buttons,
- }).open();
+ this.dialog.add(anchorDialog, props);
},
/**
* @private
diff --git a/addons/website/static/src/js/user_custom_javascript.js b/addons/website/static/src/js/user_custom_javascript.js
index 5d9827033dc..79d0188ae28 100644
--- a/addons/website/static/src/js/user_custom_javascript.js
+++ b/addons/website/static/src/js/user_custom_javascript.js
@@ -7,14 +7,17 @@
// world !' message in a popup:
//
/*
-import Dialog from '@web/legacy/js/core/dialog';
+import { ConfirmationDialog } from '@web/core/confirmation_dialog/confirmation_dialog';
import publicWidget from '@web/legacy/js/public/public_widget';
publicWidget.registry.HelloWorldPopup = publicWidget.Widget.extend({
selector: '#wrapwrap',
- start: function () {
- Dialog.alert(this, "Hello, world!");
+ init() {
+ this.dialog = this.bindService("dialog");
+ },
+ start() {
+ this.dialog.add(ConfirmationDialog, { body: 'Hello World' });
return this._super.apply(this, arguments);
},
});
diff --git a/addons/website/static/src/snippets/s_embed_code/options.js b/addons/website/static/src/snippets/s_embed_code/options.js
index 06663209dd1..a97227fba7c 100644
--- a/addons/website/static/src/snippets/s_embed_code/options.js
+++ b/addons/website/static/src/snippets/s_embed_code/options.js
@@ -1,12 +1,31 @@
/** @odoo-module **/
-
-import Dialog from '@web/legacy/js/core/dialog';
+import { Dialog } from "@web/core/dialog/dialog";
+import { CodeEditor } from "@web/core/code_editor/code_editor";
import options from '@web_editor/js/editor/snippets.options';
-import { loadBundle } from "@web/core/assets";
import { _t } from "@web/core/l10n/translation";
-import { renderToElement } from "@web/core/utils/render";
+
+import { Component, useState } from "@odoo/owl";
+
+class CodeEditorDialog extends Component {
+ static template = "website.s_embed_code_dialog";
+ static components = { Dialog, CodeEditor };
+ setup() {
+ this.state = useState({ value: this.props.value });
+ }
+ onCodeChange(newValue) {
+ this.state.value = newValue;
+ }
+ onConfirm() {
+ this.props.confirm(this.state.value);
+ this.props.close();
+ }
+}
options.registry.EmbedCode = options.Class.extend({
+ init() {
+ this._super(...arguments);
+ this.dialog = this.bindService("dialog");
+ },
//--------------------------------------------------------------------------
// Options
//--------------------------------------------------------------------------
@@ -15,72 +34,19 @@ options.registry.EmbedCode = options.Class.extend({
const $container = this.$target.find('.s_embed_code_embedded');
const code = $container.html().trim();
- await loadBundle({
- jsLibs: [
- '/web/static/lib/ace/ace.js',
- '/web/static/lib/ace/mode-xml.js',
- '/web/static/lib/ace/mode-qweb.js',
- ],
- });
-
await new Promise(resolve => {
- const $content = $(renderToElement('website.custom_code_dialog_content'));
- const aceEditor = this._renderAceEditor($content.find('.o_ace_editor_container')[0], code || '');
- const dialog = new Dialog(this, {
+ this.dialog.add(CodeEditorDialog, {
title: _t("Edit embedded code"),
- $content,
- buttons: [
- {
- text: _t("Save"),
- classes: 'btn-primary',
- click: async () => {
- $container[0].innerHTML = aceEditor.getValue();
- },
- close: true,
- },
- {
- text: _t("Discard"),
- close: true,
- },
- ],
+ value: code,
+ mode: "xml",
+ confirm: (newValue) => {
+ $container[0].innerHTML = newValue;
+ }
+ }, {
+ onClose: resolve,
});
- dialog.on('closed', this, resolve);
- dialog.open();
});
},
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * @private
- * @param {DOMElement} node
- * @param {String} content text of the editor
- * @returns {Object}
- */
- _renderAceEditor(node, content) {
- const aceEditor = window.ace.edit(node);
- aceEditor.setTheme('ace/theme/monokai');
- aceEditor.setValue(content, 1);
- aceEditor.setOptions({
- minLines: 20,
- maxLines: Infinity,
- showPrintMargin: false,
- });
- aceEditor.renderer.setOptions({
- highlightGutterLine: true,
- showInvisibles: true,
- fontSize: 14,
- });
-
- const aceSession = aceEditor.getSession();
- aceSession.setOptions({
- mode: "ace/mode/xml",
- useWorker: false,
- });
- return aceEditor;
- },
});
export default {
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 6bc61e7c129..5f9e5964bc0 100644
--- a/addons/website/static/src/snippets/s_website_form/options.js
+++ b/addons/website/static/src/snippets/s_website_form/options.js
@@ -2,8 +2,7 @@
import FormEditorRegistry from "@website/js/form_editor_registry";
import options from "@web_editor/js/editor/snippets.options";
-import Dialog from "@web/legacy/js/core/dialog";
-import dom from "@web/legacy/js/core/dom";
+import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import weUtils from "@web_editor/js/common/utils";
import "@website/js/editor/snippets.options";
import { unique } from "@web/core/utils/arrays";
@@ -365,6 +364,7 @@ options.registry.WebsiteFormEditor = FormEditor.extend({
init() {
this._super(...arguments);
this.notification = this.bindService("notification");
+ this.dialog = this.bindService("dialog");
},
/**
* @override
@@ -509,42 +509,25 @@ options.registry.WebsiteFormEditor = FormEditor.extend({
promptSaveRedirect: function (name, value, widgetValue) {
return new Promise((resolve, reject) => {
const message = _t("Would you like to save before being redirected? Unsaved changes will be discarded.");
- Dialog.confirm(this, message, {
- cancel_callback: () => resolve(),
- buttons: [
- {
- text: _t("Save"),
- classes: 'btn-primary',
- click: (ev) => {
- const restore = dom.addButtonLoadingEffect(ev.currentTarget);
- this.trigger_up('request_save', {
- reload: false,
- onSuccess: () => {
- this._redirectToAction(value);
- },
- onFailure: () => {
- restore();
- this.notification.add(_t("Something went wrong."), {
- type: 'danger',
- sticky: true,
- });
- reject();
- },
- });
- resolve();
- },
- }, {
- text: _t("Discard"),
- click: (ev) => {
- dom.addButtonLoadingEffect(ev.currentTarget);
+ this.dialog.add(ConfirmationDialog, {
+ body: message,
+ confirmLabel: _t("Save"),
+ confirm: () => {
+ this.trigger_up('request_save', {
+ reload: false,
+ onSuccess: () => {
this._redirectToAction(value);
},
- }, {
- text: _t("Cancel"),
- close: true,
- click: () => resolve(),
- },
- ],
+ onFailure: () => {
+ this.notification.add(_t("Something went wrong."), {
+ type: 'danger',
+ sticky: true,
+ });
+ reject();
+ },
+ });
+ },
+ cancel: () => resolve(),
});
});
},
@@ -1135,7 +1118,9 @@ options.registry.WebsiteFieldEditor = FieldEditor.extend({
this._setVisibilityDependency(firstValue);
return;
}
- Dialog.confirm(this, _t("There is no field available for this option."));
+ this.dialog.add(ConfirmationDialog, {
+ body: _t("There is no field available for this option."),
+ });
}
this._deleteConditionalVisibility(this.$target[0]);
},
diff --git a/addons/website/static/src/xml/website.editor.xml b/addons/website/static/src/xml/website.editor.xml
index 768e2864ab5..1389e9a6455 100644
--- a/addons/website/static/src/xml/website.editor.xml
+++ b/addons/website/static/src/xml/website.editor.xml
@@ -1,36 +1,57 @@
-
-
-
Choose an anchor name
-
-
-
-
The chosen name already exists
+
+
+
+
Choose an anchor name
+
+
+
+
The chosen name already exists
+
-
-
+
+ Save & copy
+ Discard
+
+ Remove
+
+
+
+
-
-
-
Google Font address
-
-
-
- Select one font on fonts.google.com and copy paste the address of the font page here.
-
+
+
+
+
Google Font address
+
+
+
+ Select one font on fonts.google.com and copy paste the address of the font page here.
+
+
+
Serve font from Google servers
+
+
+
+
- Serve font from Google servers
-
-
-
-
-
-
+
+
+
+
+
+ Save and Reload
+
+ Cancel
+
+
+
Delete this font
@@ -69,58 +90,73 @@
-
-
Use Google Map on your website (Contact Us page, snippets, etc).
-
-
API Key
-
-
-
-
-
-
- Make sure your settings are properly configured:
-
-
- Enable the right google map APIs in your google account
-
- Maps Static API
- Maps JavaScript API
- Places API
-
-
-
- Make sure billing is enabled
-
-
- Make sure to wait if errors keep being shown: sometimes enabling an API allows to use it immediately but Google keeps triggering errors for a while
-
-
+
+
+ Use Google Map on your website (Contact Us page, snippets, etc).
+
+
API Key
+
+
+
+
+
+
+ Make sure your settings are properly configured:
+
+
+ Enable the right google map APIs in your google account
+
+ Maps Static API
+ Maps JavaScript API
+ Places API
+
+
+
+ Make sure billing is enabled
+
+
+ Make sure to wait if errors keep being shown: sometimes enabling an API allows to use it immediately but Google keeps triggering errors for a while
+
+
+
-
-
+
+ Save
+ Cancel
+
+
+
-
+
+
+
+
+ Save
+ Discard
+
+
+
diff --git a/addons/website_sale/static/src/js/website_sale.editor.js b/addons/website_sale/static/src/js/website_sale.editor.js
index 46e2e628b0a..ec84255a2ee 100644
--- a/addons/website_sale/static/src/js/website_sale.editor.js
+++ b/addons/website_sale/static/src/js/website_sale.editor.js
@@ -2,8 +2,8 @@
import options from "@web_editor/js/editor/snippets.options";
import { MediaDialog } from "@web_editor/components/media_dialog/media_dialog";
+import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
import { _t } from "@web/core/l10n/translation";
-import Dialog from "@web/legacy/js/core/dialog";
import "@website/js/editor/snippets.options";
import { renderToElement } from "@web/core/utils/render";
@@ -186,9 +186,10 @@ options.registry.WebsiteSaleProductsItem = options.Class.extend({
*/
async deleteRibbon(previewMode, widgetValue, params) {
const save = await new Promise(resolve => {
- Dialog.confirm(this, _t('Are you sure you want to delete this badge?'), {
- confirm_callback: () => resolve(true),
- cancel_callback: () => resolve(false),
+ this.dialog.add(ConfirmationDialog, {
+ body: _t('Are you sure you want to delete this badge?'),
+ confirm: () => resolve(true),
+ cancel: () => resolve(false),
});
});
if (!save) {