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 fc36ed00409d1..5607919b55474 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -584,14 +584,15 @@ var SnippetEditor = Widget.extend({ await focusOrBlur(editor, styles); } await Promise.all(editorUIsToUpdate.map(editor => editor.updateOptionsUI())); - await Promise.all(editorUIsToUpdate.map(editor => editor.updateOptionsUIVisibility())); - - // As the 'd-none' class is added to option sections that have no visible - // options with 'updateOptionsUIVisibility', if no option section is - // visible, we prevent the activation of the options. - const optionsSectionVisible = editorUIsToUpdate.some( - editor => !editor.$optionsSection[0].classList.contains('d-none') - ); + // A `d-none` class is added to option sections that have no visible + // options with `updateOptionsUIVisibility`. If no option section is + // visible (including the options moved to the toolbar), we prevent + // the activation of the options. + const optionsSectionVisible = await Promise.all( + editorUIsToUpdate.map((editor) => editor.updateOptionsUIVisibility()) + ).then(editorVisibilityValues => { + return editorVisibilityValues.some(editorVisibilityValue => editorVisibilityValue) + }); if (editorUIsToUpdate.length > 0 && !optionsSectionVisible) { return null; } @@ -651,7 +652,22 @@ var SnippetEditor = Widget.extend({ // because some options can be located in the overlay. const $visibleOptions = this.$optionsSection.find('we-top-button-group, we-customizeblock-option') .children(':not(.d-none)'); - this.$optionsSection.toggleClass('d-none', !$visibleOptions.length); + // Some options (e.g., text highlights / animations) may have a special + // way to be displayed in the editor: We add the options in the toolbar + // `onFocus()` and set them back `onBlur()`. Which means that the + // options section will be empty and should be hidden, while editor's + // visible options should be displayed in the toolbar DOM. We need to + // take this scenario into consideration too. + const optionsSectionEmpty = !this.$optionsSection[0].querySelector(":scope > we-customizeblock-option"); + const optionsSectionVisible = $visibleOptions.length && !optionsSectionEmpty; + // At this level, we can hide the options section. + this.$optionsSection.toggleClass("d-none", !optionsSectionVisible); + // Even with a hidden options section, the editor is still considered + // visible" if it has visible toolbar options. + const visibleToolbarOptions = Object.values(this.styles).some( + (option) => option.el.closest(".oe-toolbar") && !option.el.classList.contains("d-none") + ); + return optionsSectionVisible || visibleToolbarOptions; }, /** * Clones the current snippet. @@ -1965,33 +1981,6 @@ var SnippetsMenu = Widget.extend({ }, }); - if (this.options.enableTranslation) { - // Load the sidebar with the style tab only. - await this._loadSnippetsTemplates(); - defs.push(this._updateInvisibleDOM()); - this.$el.find('.o_we_website_top_actions').removeClass('d-none'); - this.$('.o_snippet_search_filter').addClass('d-none'); - this.$('#o_scroll').addClass('d-none'); - this.$('button[data-action="mobilePreview"]').addClass('d-none'); - this.$('#snippets_menu button').removeClass('active').prop('disabled', true); - this.$('.o_we_customize_snippet_btn').addClass('active').prop('disabled', false); - this.$('o_we_ui_loading').addClass('d-none'); - $(this.customizePanel).removeClass('d-none'); - this.$('#o_we_editor_toolbar_container').hide(); - this.$('#o-we-editor-table-container').addClass('d-none'); - return Promise.all(defs); - } - - this.emptyOptionsTabContent = document.createElement('div'); - this.emptyOptionsTabContent.classList.add('text-center', 'pt-5'); - this.emptyOptionsTabContent.append(_t("Select a block on your page to style it.")); - - // Fetch snippet templates and compute it - defs.push((async () => { - await this._loadSnippetsTemplates(this.options.invalidateSnippetCache); - await this._updateInvisibleDOM(); - })()); - // Active snippet editor on click in the page this.$document.on('click.snippets_menu', '*', this._onClick); // Needed as bootstrap stop the propagation of click events for dropdowns @@ -2053,6 +2042,33 @@ var SnippetsMenu = Widget.extend({ // scrolling a modal) this.$scrollingTarget[0].addEventListener('scroll', this._onScrollingElementScroll, {capture: true}); + if (this.options.enableTranslation) { + // Load the sidebar with the style tab only. + await this._loadSnippetsTemplates(); + defs.push(this._updateInvisibleDOM()); + this.$el.find('.o_we_website_top_actions').removeClass('d-none'); + this.$('.o_snippet_search_filter').addClass('d-none'); + this.$('#o_scroll').addClass('d-none'); + this.$('button[data-action="mobilePreview"]').addClass('d-none'); + this.$('#snippets_menu button').removeClass('active').prop('disabled', true); + this.$('.o_we_customize_snippet_btn').addClass('active').prop('disabled', false); + this.$('o_we_ui_loading').addClass('d-none'); + $(this.customizePanel).removeClass('d-none'); + this.$('#o_we_editor_toolbar_container').hide(); + this.$('#o-we-editor-table-container').addClass('d-none'); + return Promise.all(defs).then(() => {}); + } + + this.emptyOptionsTabContent = document.createElement('div'); + this.emptyOptionsTabContent.classList.add('text-center', 'pt-5'); + this.emptyOptionsTabContent.append(_t("Select a block on your page to style it.")); + + // Fetch snippet templates and compute it + defs.push((async () => { + await this._loadSnippetsTemplates(this.options.invalidateSnippetCache); + await this._updateInvisibleDOM(); + })()); + // Auto-selects text elements with a specific class and remove this // on text changes const alreadySelectedElements = new Set(); @@ -2261,7 +2277,18 @@ var SnippetsMenu = Widget.extend({ } this._mutex.exec(() => { if (this._currentTab === this.tabs.OPTIONS && !this.snippetEditors.length) { - this._activateEmptyOptionsTab(); + const selection = this.$body[0].ownerDocument.getSelection(); + const range = selection?.rangeCount && selection.getRangeAt(0); + const currentlySelectedNode = range?.commonAncestorContainer; + // In some cases (e.g. in translation mode) it's possible to have + // all snippet editors destroyed after disabling text options. + // We still want to keep the toolbar available in this case. + const isEditableTextElementSelected = + currentlySelectedNode?.nodeType === Node.TEXT_NODE && + !!currentlySelectedNode?.parentNode?.isContentEditable; + if (!isEditableTextElementSelected) { + this._activateEmptyOptionsTab(); + } } }); }, @@ -2655,12 +2682,6 @@ var SnippetsMenu = Widget.extend({ * (might be async when an editor must be created) */ _activateSnippet: async function ($snippet, previewMode, ifInactiveOptions) { - if (this.options.enableTranslation) { - // In translate mode, do not activate the snippet when enabling its - // corresponding invisible element. Indeed, in translate mode, we - // only want to toggle its visibility. - return; - } if (this._blockPreviewOverlays && previewMode) { return; } @@ -3039,9 +3060,9 @@ var SnippetsMenu = Widget.extend({ } return $target; }; - globalSelector.is = function ($from) { + globalSelector.is = function ($from, options = {}) { for (var i = 0, len = selectors.length; i < len; i++) { - if (selectors[i].is($from)) { + if (options.onlyTextOptions ? $from.is(self.templateOptions[i].data.textSelector) : selectors[i].is($from)) { return true; } } @@ -3159,6 +3180,12 @@ var SnippetsMenu = Widget.extend({ return snippetEditor.__isStarted; } + // In translate mode, only allow creating the editor if the target is a + // text option snippet. + if (this.options.enableTranslation && !this._allowInTranslationMode($snippet)) { + return Promise.resolve(null); + } + var def; if (this._allowParentsEditors($snippet)) { var $parent = globalSelector.closest($snippet.parent()); @@ -3606,6 +3633,11 @@ var SnippetsMenu = Widget.extend({ this._hideTooltips(); this._closeWidgets(); + // In translation mode, only the options tab is available. + if (this.options.enableTranslation) { + tab = this.tabs.OPTIONS; + } + this._currentTab = tab || this.tabs.BLOCKS; if (this._$toolbarContainer) { @@ -3769,6 +3801,12 @@ var SnippetsMenu = Widget.extend({ return !this.options.enableTranslation && !$snippet[0].classList.contains("o_no_parent_editor"); }, + /** + * @private + */ + _allowInTranslationMode($snippet) { + return globalSelector.is($snippet, { onlyTextOptions: true }); + }, //-------------------------------------------------------------------------- // Handlers diff --git a/addons/website/static/src/js/editor/snippets.editor.js b/addons/website/static/src/js/editor/snippets.editor.js index 34ae0125aac36..81b7a748494e2 100644 --- a/addons/website/static/src/js/editor/snippets.editor.js +++ b/addons/website/static/src/js/editor/snippets.editor.js @@ -202,6 +202,17 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({ )[0]; element.remove(); }); + + // TODO remove in master: should be simply replaced by a + // `data-text-selector` attribute to mark text options. + const AnimationOptionEl = $html.find('[data-js="WebsiteAnimate"]')[0]; + const HighlightOptionEl = $html.find('[data-js="TextHighlight"]')[0]; + if (AnimationOptionEl) { + AnimationOptionEl.dataset.textSelector = ".o_animated_text"; + } + if (HighlightOptionEl) { + HighlightOptionEl.dataset.textSelector = HighlightOptionEl.dataset.selector; + } }, /** * Depending of the demand, reconfigure they gmap key or configure it @@ -481,6 +492,9 @@ const wSnippetMenu = weSnippetEditor.SnippetsMenu.extend({ this._disableTextOptions(targetEl); this.options.wysiwyg.odooEditor.historyStep(true); restoreCursor(); + if (this.options.enableTranslation) { + $(selectedTextParent).trigger("content_changed"); + } } else { if (sel.getRangeAt(0).collapsed) { return; @@ -762,20 +776,6 @@ weSnippetEditor.SnippetEditor.include({ } return this._super(...arguments); }, - /** - * @override - * @returns {Promise} - */ - async updateOptionsUIVisibility() { - await this._super(...arguments); - // TODO improve this: some website text options (like text animations, - // text highlights...) are moved to the toolbar, which leads to an empty - // "options section". The goal of this override is to hide options - // sections with no option elements. - if (!this.$optionsSection[0].querySelector(":scope > we-customizeblock-option")) { - this.$optionsSection[0].classList.add("d-none"); - } - }, /** * Changes some behaviors before the drag and drop. * @@ -836,9 +836,6 @@ wSnippetMenu.include({ */ start() { const _super = this._super(...arguments); - if (this.options.enableTranslation) { - return _super; - } if (this.$body[0].ownerDocument !== this.ownerDocument) { this.$body.on('click.snippets_menu', '*', this._onClick); }