odoo.define('website.root', function (require) { 'use strict'; const ajax = require('web.ajax'); const {_t} = require('web.core'); var Dialog = require('web.Dialog'); const KeyboardNavigationMixin = require('web.KeyboardNavigationMixin'); const session = require('web.session'); var publicRootData = require('web.public.root'); require("web.zoomodoo"); var websiteRootRegistry = publicRootData.publicRootRegistry; var WebsiteRoot = publicRootData.PublicRoot.extend(KeyboardNavigationMixin, { events: _.extend({}, KeyboardNavigationMixin.events, publicRootData.PublicRoot.prototype.events || {}, { 'click .js_change_lang': '_onLangChangeClick', 'click .js_publish_management .js_publish_btn': '_onPublishBtnClick', 'click .js_multi_website_switch': '_onWebsiteSwitch', 'shown.bs.modal': '_onModalShown', }), custom_events: _.extend({}, publicRootData.PublicRoot.prototype.custom_events || {}, { 'gmap_api_request': '_onGMapAPIRequest', 'gmap_api_key_request': '_onGMapAPIKeyRequest', 'ready_to_clean_for_save': '_onWidgetsStopRequest', 'seo_object_request': '_onSeoObjectRequest', }), /** * @override */ init() { this.isFullscreen = false; KeyboardNavigationMixin.init.call(this, { autoAccessKeys: false, }); return this._super(...arguments); }, /** * @override */ start: function () { KeyboardNavigationMixin.start.call(this); // Compatibility lang change ? if (!this.$('.js_change_lang').length) { var $links = this.$('.js_language_selector a:not([data-oe-id])'); var m = $(_.min($links, function (l) { return $(l).attr('href').length; })).attr('href'); $links.each(function () { var $link = $(this); var t = $link.attr('href'); var l = (t === m) ? "default" : t.split('/')[1]; $link.data('lang', l).addClass('js_change_lang'); }); } // Enable magnify on zommable img this.$('.zoomable img[data-zoom]').zoomOdoo(); return this._super.apply(this, arguments); }, /** * @override */ destroy() { KeyboardNavigationMixin.destroy.call(this); return this._super(...arguments); }, //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- /** * @override */ _getContext: function (context) { var html = document.documentElement; return _.extend({ 'website_id': html.getAttribute('data-website-id') | 0, }, this._super.apply(this, arguments)); }, /** * @override */ _getExtraContext: function (context) { var html = document.documentElement; return _.extend({ 'editable': !!(html.dataset.editable || $('[data-oe-model]').length), // temporary hack, this should be done in python 'translatable': !!html.dataset.translatable, 'edit_translations': !!html.dataset.edit_translations, }, this._super.apply(this, arguments)); }, /** * @private * @param {boolean} [refetch=false] */ async _getGMapAPIKey(refetch) { if (refetch || !this._gmapAPIKeyProm) { this._gmapAPIKeyProm = new Promise(async resolve => { const data = await this._rpc({ route: '/website/google_maps_api_key', }); resolve(JSON.parse(data).google_maps_api_key || ''); }); } return this._gmapAPIKeyProm; }, /** * @override */ _getPublicWidgetsRegistry: function (options) { var registry = this._super.apply(this, arguments); if (options.editableMode) { return _.pick(registry, function (PublicWidget) { return !PublicWidget.prototype.disabledInEditableMode; }); } return registry; }, /** * @private * @param {boolean} [editableMode=false] * @param {boolean} [refetch=false] */ async _loadGMapAPI(editableMode, refetch) { // Note: only need refetch to reload a configured key and load the // library. If the library was loaded with a correct key and that the // key changes meanwhile... it will not work but we can agree the user // can bother to reload the page at that moment. if (refetch || !this._gmapAPILoading) { this._gmapAPILoading = new Promise(async resolve => { const key = await this._getGMapAPIKey(refetch); window.odoo_gmap_api_post_load = (async function odoo_gmap_api_post_load() { await this._startWidgets(undefined, {editableMode: editableMode}); resolve(key); }).bind(this); if (!key) { if (!editableMode && session.is_admin) { this.displayNotification({ type: 'warning', sticky: true, message: $('
').append( $('', {text: _t("Cannot load google map.")}), $('
'), $('', { href: "/web#action=website.action_website_configuration", text: _t("Check your configuration."), }), )[0].outerHTML, }); } resolve(false); this._gmapAPILoading = false; return; } await ajax.loadJS(`https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=places&callback=odoo_gmap_api_post_load&key=${key}`); }); } return this._gmapAPILoading; }, /** * Toggles the fullscreen mode. * * @private * @param {boolean} state toggle fullscreen on/off (true/false) */ _toggleFullscreen(state) { this.isFullscreen = state; document.body.classList.add('o_fullscreen_transition'); document.body.classList.toggle('o_fullscreen', this.isFullscreen); document.body.style.overflowX = 'hidden'; let resizing = true; window.requestAnimationFrame(function resizeFunction() { window.dispatchEvent(new Event('resize')); if (resizing) { window.requestAnimationFrame(resizeFunction); } }); let stopResizing; const onTransitionEnd = ev => { if (ev.target === document.body && ev.propertyName === 'padding-top') { stopResizing(); } }; stopResizing = () => { resizing = false; document.body.style.overflowX = ''; document.body.removeEventListener('transitionend', onTransitionEnd); document.body.classList.remove('o_fullscreen_transition'); }; document.body.addEventListener('transitionend', onTransitionEnd); // Safeguard in case the transitionend event doesn't trigger for whatever reason. window.setTimeout(() => stopResizing(), 500); }, //-------------------------------------------------------------------------- // Handlers //-------------------------------------------------------------------------- /** * @override */ _onWidgetsStartRequest: function (ev) { ev.data.options = _.clone(ev.data.options || {}); ev.data.options.editableMode = ev.data.editableMode; this._super.apply(this, arguments); }, /** * @todo review * @private */ _onLangChangeClick: function (ev) { ev.preventDefault(); var $target = $(ev.currentTarget); // retrieve the hash before the redirect var redirect = { lang: $target.data('url_code'), 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); }, /** * @private * @param {OdooEvent} ev */ async _onGMapAPIRequest(ev) { ev.stopPropagation(); const apiKey = await this._loadGMapAPI(ev.data.editableMode, ev.data.refetch); ev.data.onSuccess(apiKey); }, /** * @private * @param {OdooEvent} ev */ async _onGMapAPIKeyRequest(ev) { ev.stopPropagation(); const apiKey = await this._getGMapAPIKey(ev.data.refetch); ev.data.onSuccess(apiKey); }, /** /** * Checks information about the page SEO object. * * @private * @param {OdooEvent} ev */ _onSeoObjectRequest: function (ev) { var res = this._unslugHtmlDataObject('seo-object'); ev.data.callback(res); }, /** * Returns a model/id object constructed from html data attribute. * * @private * @param {string} dataAttr * @returns {Object} an object with 2 keys: model and id, or null * if not found */ _unslugHtmlDataObject: function (dataAttr) { var repr = $('html').data(dataAttr); var match = repr && repr.match(/(.+)\((\d+),(.*)\)/); if (!match) { return null; } return { model: match[1], id: match[2] | 0, }; }, /** * @todo review * @private */ _onPublishBtnClick: function (ev) { ev.preventDefault(); if (document.body.classList.contains('editor_enable')) { return; } var self = this; var $data = $(ev.currentTarget).parents(".js_publish_management:first"); this._rpc({ route: $data.data('controller') || '/website/publish', params: { id: +$data.data('id'), object: $data.data('object'), }, }) .then(function (result) { $data.toggleClass("css_unpublished css_published"); $data.find('input').prop("checked", result); $data.parents("[data-publish]").attr("data-publish", +result ? 'on' : 'off'); if (result) { self.displayNotification({ type: 'success', message: $data.data('description') ? _.str.sprintf(_t("You've published your %s."), $data.data('description')) : _t("Published with success."), }); } }); }, /** * @private * @param {Event} ev */ _onWebsiteSwitch: function (ev) { var websiteId = ev.currentTarget.getAttribute('website-id'); var websiteDomain = ev.currentTarget.getAttribute('domain'); let url = `/website/force/${websiteId}`; if (websiteDomain && window.location.hostname !== websiteDomain) { url = websiteDomain + url; } const path = window.location.pathname + window.location.search + window.location.hash; window.location.href = $.param.querystring(url, {'path': path}); }, /** * @private * @param {Event} ev */ _onModalShown: function (ev) { $(ev.target).addClass('modal_shown'); }, /** * @override */ _onKeyDown(ev) { if (!session.user_id) { return; } // If document.body doesn't contain the element, it was probably removed as a consequence of pressing Esc. // we don't want to toggle fullscreen as the removal (eg, closing a modal) is the intended action. if (ev.keyCode !== $.ui.keyCode.ESCAPE || !document.body.contains(ev.target) || ev.target.closest('.modal')) { return KeyboardNavigationMixin._onKeyDown.apply(this, arguments); } this._toggleFullscreen(!this.isFullscreen); }, }); return { WebsiteRoot: WebsiteRoot, websiteRootRegistry: websiteRootRegistry, }; });