[REF] website, *: replace legacy dialog with OWL Dialog
*: test_website, web_editor, website_sale The goal of this commit is to replace all Dialog in the Website Editor flow with OWL Dialog. This uses the new `bindService` and `call` methods available on legacy widgets. The replacements mainly use the ConfirmationDialog component, but some had to have custom-made dialogs. closes odoo/odoo#138947 Signed-off-by: Francois Georis (fge) <fge@odoo.com>
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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: $('<div/>', {text: _t("Do you want to install the %s App?", name)}).append(
|
||||
$('<a/>', {
|
||||
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 <strong>%s</strong>", escape(name)));
|
||||
self.notification.add(message, {
|
||||
type: 'danger',
|
||||
sticky: true,
|
||||
});
|
||||
} else {
|
||||
return Promise.reject(reason);
|
||||
}
|
||||
body: markup(`${escape(bodyText)}\n<a href="${linkUrl}" target="_blank">${escape(linkText)}</a>`),
|
||||
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: $('<div><p>' + _t("Are you sure you want to delete the snippet: %s?", $snippet.attr('name')) + '</p></div>'),
|
||||
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
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
},
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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]);
|
||||
},
|
||||
|
||||
@@ -1,36 +1,57 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<templates id="template" xml:space="preserve">
|
||||
<!-- Anchor Name option dialog -->
|
||||
<div t-name="website.dialog.anchorName">
|
||||
<div class="mb-3 row">
|
||||
<label class="col-form-label col-md-3" for="anchorName">Choose an anchor name</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control o_input_anchor_name" id="anchorName" t-attf-value="#{currentAnchor}" placeholder="Anchor name"/>
|
||||
<div class="invalid-feedback">
|
||||
<p class="d-none o_anchor_already_exists">The chosen name already exists</p>
|
||||
<t t-name="website.dialog.anchorName">
|
||||
<Dialog title="this.title" size="'lg'" modalRef="this.modalRef">
|
||||
<div class="mb-3 row">
|
||||
<label class="col-form-label col-md-3" for="anchorName">Choose an anchor name</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" class="form-control o_input_anchor_name" id="anchorName"
|
||||
t-attf-value="#{props.currentAnchor}" placeholder="Anchor name"/>
|
||||
<div class="invalid-feedback">
|
||||
<p class="d-none o_anchor_already_exists">The chosen name already exists</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<t t-set-slot="footer">
|
||||
<button class="btn btn-primary" t-on-click="onClickConfirm">Save & copy</button>
|
||||
<button class="btn btn-secondary" t-on-click="onClickDiscard">Discard</button>
|
||||
<button t-if="props.delete" class="btn btn-link ms-auto" t-on-click="onClickDelete"><i class="fa fa-icon fa-trash"/>
|
||||
Remove
|
||||
</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
</t>
|
||||
|
||||
<!-- Add a Google Font option dialog -->
|
||||
<div t-name="website.dialog.addGoogleFont">
|
||||
<div class="mb-3 row">
|
||||
<label class="col-form-label col-md-3" for="google_font_html">Google Font address</label>
|
||||
<div class="col-md-9">
|
||||
<textarea id="google_font_html" class="form-control o_input_google_font"
|
||||
placeholder="https://fonts.google.com/specimen/Roboto" style="height: 100px;"/>
|
||||
<span class="float-end text-muted">
|
||||
Select one font on <a target="_blank" href="https://fonts.google.com">fonts.google.com</a> and copy paste the address of the font page here.
|
||||
</span>
|
||||
<t t-name="website.dialog.addGoogleFont">
|
||||
<Dialog title="this.props.title">
|
||||
<div class="mb-3 row">
|
||||
<label class="col-form-label col-md-3" for="google_font_html">Google Font address</label>
|
||||
<div class="col-md-9">
|
||||
<textarea id="google_font_html" class="form-control o_input_google_font" t-ref="fontInput"
|
||||
placeholder="https://fonts.google.com/specimen/Roboto" style="height: 100px;"/>
|
||||
<span class="float-end text-muted">
|
||||
Select one font on <a target="_blank" href="https://fonts.google.com">fonts.google.com</a> and copy paste the address of the font page here.
|
||||
</span>
|
||||
</div>
|
||||
<label class="col-form-label col-md-3" for="google_font_serve">Serve font from Google servers</label>
|
||||
<label class="o_switch col-form-label col-md-9" for="google_font_serve">
|
||||
<input type="checkbox" checked="checked" id="google_font_serve" t-model="state.googleServe"/>
|
||||
<span/>
|
||||
</label>
|
||||
</div>
|
||||
<label class="col-form-label col-md-3" for="google_font_serve">Serve font from Google servers</label>
|
||||
<label class="o_switch col-form-label col-md-9" for="google_font_serve">
|
||||
<input type="checkbox" checked="checked" id="google_font_serve"/>
|
||||
<span/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<t t-set-slot="footer">
|
||||
<button class="btn btn-primary" t-att-disabled="state.loading" t-on-click="onClickSave">
|
||||
<t t-if="this.state.loading">
|
||||
<i class="fa fa-icon fa-icon"/>
|
||||
</t>
|
||||
Save and Reload
|
||||
</button>
|
||||
<button class="btn btn-secondary" t-on-click="onClickCancel">Cancel</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
</t>
|
||||
<t t-name="website.delete_google_font_btn">
|
||||
<i t-if="!local" role="button" class="text-info me-2 fa fa-cloud" title="This font is hosted and served to your visitors by Google servers"/>
|
||||
<t t-set="delete_font_title">Delete this font</t>
|
||||
@@ -69,58 +90,73 @@
|
||||
<we-colorpicker title="Border" data-customize-website-color="null" t-attf-data-color="o-cc#{number}-btn-secondary-border"/>
|
||||
</we-row>
|
||||
</t>
|
||||
<div t-name="website.s_google_map_modal">
|
||||
<p>Use Google Map on your website (Contact Us page, snippets, etc).</p>
|
||||
<div class="row mb-0">
|
||||
<label class="col-sm-2 col-form-label" for="pin_address">API Key</label>
|
||||
<div class="col">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text"><i class="fa fa-key"/></div>
|
||||
<input type="text" class="form-control" id="api_key_input"
|
||||
t-att-value="apiKey or ''"
|
||||
placeholder="BSgzTvR5L1GB9jriT451iTN4huVPxHmltG6T6eo"/>
|
||||
</div>
|
||||
<small id="api_key_help" class="text-danger">
|
||||
</small>
|
||||
<div class="small form-text text-muted">
|
||||
Hint: How to use Google Map on your website (Contact Us page and as a snippet)
|
||||
<br/>
|
||||
<a target="_blank" href="https://console.developers.google.com/flows/enableapi?apiid=maps_backend,static_maps_backend&keyType=CLIENT_SIDE&reusekey=true">
|
||||
<i class="oi oi-arrow-right"/>
|
||||
Create a Google Project and Get a Key
|
||||
</a>
|
||||
<br/>
|
||||
<a target="_blank" href="https://cloud.google.com/maps-platform/pricing">
|
||||
<i class="oi oi-arrow-right"/>
|
||||
Enable billing on your Google Project
|
||||
</a>
|
||||
</div>
|
||||
<div class="alert alert-info mb-0 mt-3">
|
||||
Make sure your settings are properly configured:
|
||||
<ul class="mb-0">
|
||||
<li>
|
||||
Enable the right google map APIs in your google account
|
||||
<ul>
|
||||
<li>Maps Static API</li>
|
||||
<li>Maps JavaScript API</li>
|
||||
<li>Places API</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Make sure billing is enabled
|
||||
</li>
|
||||
<li>
|
||||
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
|
||||
</li>
|
||||
</ul>
|
||||
<t t-name="website.s_google_map_modal">
|
||||
<Dialog title="props.title" size="'md'" modalRef="modalRef">
|
||||
<p>Use Google Map on your website (Contact Us page, snippets, etc).</p>
|
||||
<div class="row mb-0">
|
||||
<label class="col-sm-2 col-form-label" for="pin_address">API Key</label>
|
||||
<div class="col">
|
||||
<div class="input-group">
|
||||
<div class="input-group-text"><i class="fa fa-key"/></div>
|
||||
<input type="text" class="form-control" id="api_key_input"
|
||||
t-model="this.state.apiKey"
|
||||
placeholder="BSgzTvR5L1GB9jriT451iTN4huVPxHmltG6T6eo"/>
|
||||
</div>
|
||||
<small id="api_key_help" class="text-danger">
|
||||
</small>
|
||||
<div class="small form-text text-muted">
|
||||
Hint: How to use Google Map on your website (Contact Us page and as a snippet)
|
||||
<br/>
|
||||
<a target="_blank" href="https://console.developers.google.com/flows/enableapi?apiid=maps_backend,static_maps_backend&keyType=CLIENT_SIDE&reusekey=true">
|
||||
<i class="oi oi-arrow-right"/>
|
||||
Create a Google Project and Get a Key
|
||||
</a>
|
||||
<br/>
|
||||
<a target="_blank" href="https://cloud.google.com/maps-platform/pricing">
|
||||
<i class="oi oi-arrow-right"/>
|
||||
Enable billing on your Google Project
|
||||
</a>
|
||||
</div>
|
||||
<div class="alert alert-info mb-0 mt-3">
|
||||
Make sure your settings are properly configured:
|
||||
<ul class="mb-0">
|
||||
<li>
|
||||
Enable the right google map APIs in your google account
|
||||
<ul>
|
||||
<li>Maps Static API</li>
|
||||
<li>Maps JavaScript API</li>
|
||||
<li>Places API</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Make sure billing is enabled
|
||||
</li>
|
||||
<li>
|
||||
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
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<t t-set-slot="footer">
|
||||
<button t-on-click="onClickSave" class="btn btn-primary">Save</button>
|
||||
<button class="btn" t-on-click="() => this.props.close()">Cancel</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
</t>
|
||||
|
||||
<!-- Theme - custom code -->
|
||||
<div t-name="website.custom_code_dialog_content">
|
||||
<div class="mb-2" t-esc="contentText"/>
|
||||
<div class="o_ace_editor_container"/>
|
||||
</div>
|
||||
<t t-name="website.s_embed_code_dialog">
|
||||
<Dialog title="props.title">
|
||||
<CodeEditor mode="props.mode"
|
||||
class="'o_website_code_editor_field'"
|
||||
theme="'monokai'"
|
||||
onChange.bind="onCodeChange"
|
||||
value="this.state.value"/>
|
||||
<t t-set-slot="footer">
|
||||
<button class="btn btn-primary" t-on-click="onConfirm">Save</button>
|
||||
<button class="btn" t-on-click="this.props.close">Discard</button>
|
||||
</t>
|
||||
</Dialog>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user