From 164df578fcf601bed34413ba289dfc8ae20da22b Mon Sep 17 00:00:00 2001 From: Rodolpho Lima Date: Fri, 3 Mar 2023 18:36:26 +0000 Subject: [PATCH] [FIX] mass_mailing, web_editor: add missing toolbar in basic theme Short read version: This commit gives the Wysiwyg class the attribution of switching between the two types of text editor toolbar (floating or part of the sidebar), instead of the SnippetsMenu class, as Wysiwyg owns the Toolbar instance and has the methods to configure it. The switching between toolbars is now done by having one instance of each type (and properly displaying/hiding them), rather than moving the toolbar element of place in the DOM and reconfiguring it (button handlers, CSS classes, displayed/hidded buttons, etc), so that the configuration steps of each toolbar can be done only once. The floating toolbar is needed for mailings based on the "basic" theme, where no sidebar/SnippetsMenu is available. Note that it will be only instantiated if needed. Long read version: The 'basic' theme in mass_mailing has no sidebar (SnippetsMenu), so the floating toolbar should be available instead, in order to give users access to the text editor's features. Commit [1] added the `setFolded` method to the SnippetsMenu class, which folds (hides) the snippets menu and recovers the floating toolbar from the sidebar container and vice-versa. While the recovered toolbar works well when `isMobile` is `true` (although with some minor issues described below), the one for the non-mobile case does not (as it must be properly reconfigured using the document's Jquery, and not the iframe's, among other details). This commit proposes a different approach to the toolbar recovery issue: if the floating toolbar is needed (mobile or basic theme), a new Toolbar instance is instantiated and properly configured. That way the Wysiwyg instance will hold two instances of the toolbar: the one embedded in the sidebar (snippets menu) and the floating one. This allows for easy switching between the two types of toolbar, without the need to run the configuration steps for the toolbar on every switch. Such toolbar switching is needed in mass_mailing, where a single instance of the wysiwyg is kept while the user can switch between different records of mailings. Some of these records could be based on the 'basic' theme (thus, no sidebar), and other ones based on other themes that do offer the snippets menu. After this commit, the toolbar switching is handled by the Wysiwyg class instead of SnippetsMenu, as Wysiwyg owns the Toolbar instance(s) and has the methods to configure it. The `folded` property of SnippetsMenu and the `setFolded` method are leveraged by this commit, keeping its original purpose and meaning (except for the toolbar handling). Before this commit the floating toolbar for the mobile case had the following issues, that are fixed by this commit: - The "table" button was (incorrectly) displayed; - The default colors of the color picker (the top row) were undefined; - The 'Link' powerbox command and the toolbar icon should open the Dialog, and not the LinkTools (part of the sidebar). Finally, this commit removes the SCSS rules that hide the SnippetsMenu when the theme in "basic", as its display is already handled by SnippetsMenu's `setFolded` method, introduced by [1]. [1] https://github.com/odoo/odoo/pull/105472/commits/c589939eeaff928997bf6f9735b19710036f9ad6 task-3133259 opw-3165111 Part-of: odoo/odoo#117166 --- .../static/src/js/mass_mailing_html_field.js | 13 ++-- addons/mass_mailing/static/src/js/wysiwyg.js | 62 ++++++++++++++++++- .../static/src/scss/mass_mailing.ui.scss | 4 -- .../js/editor/odoo-editor/src/OdooEditor.js | 62 ++++++++++--------- .../static/src/js/editor/snippets.editor.js | 47 +++++--------- .../static/src/js/wysiwyg/wysiwyg.js | 8 +-- 6 files changed, 119 insertions(+), 77 deletions(-) diff --git a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js index 9af8053cbd1..6395eb479ec 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js @@ -46,11 +46,11 @@ export class MassMailingHtmlField extends HtmlField { return { ...super.wysiwygOptions, onIframeUpdated: () => this.onIframeUpdated(), - foldSnippets: device.isMobile, snippets: 'mass_mailing.email_designer_snippets', resizable: false, defaultDataForLinkTools: { isNewWindow: true }, - toolbarTemplate: device.isMobile ? 'web_editor.toolbar' : 'mass_mailing.web_editor_toolbar', + toolbarTemplate: 'mass_mailing.web_editor_toolbar', + onWysiwygBlur: () => this.wysiwyg.odooEditor.toolbarHide(), ...this.props.wysiwygOptions, }; } @@ -337,7 +337,7 @@ export class MassMailingHtmlField extends HtmlField { if (editableAreaIsEmpty) { // unfold to prevent toolbar from going over the menu - this.wysiwyg.snippetsMenu.setFolded(false); + this.wysiwyg.setSnippetsMenuFolded(false); $themeSelectorNew.appendTo(this.wysiwyg.$iframeBody); } @@ -353,9 +353,8 @@ export class MassMailingHtmlField extends HtmlField { this.wysiwyg.$iframeBody.closest('body').removeClass("o_force_mail_theme_choice"); $themeSelectorNew.remove(); - if (device.isMobile) { - this.wysiwyg.snippetsMenu.setFolded(true); - } + + this.wysiwyg.setSnippetsMenuFolded(device.isMobile || themeName === 'basic'); this._switchImages(themeParams, $snippets); @@ -424,6 +423,8 @@ export class MassMailingHtmlField extends HtmlField { selectedTheme = this._getSelectedTheme(themesParams); } + this.wysiwyg.setSnippetsMenuFolded(device.isMobile || (selectedTheme && selectedTheme.name === 'basic')); + this.wysiwyg.$iframeBody.find('.iframe-utils-zone').removeClass('d-none'); if (this.env.mailingFilterTemplates && this.wysiwyg) { this._hideIrrelevantTemplates(); diff --git a/addons/mass_mailing/static/src/js/wysiwyg.js b/addons/mass_mailing/static/src/js/wysiwyg.js index d95542583a1..5cc4d285411 100644 --- a/addons/mass_mailing/static/src/js/wysiwyg.js +++ b/addons/mass_mailing/static/src/js/wysiwyg.js @@ -3,6 +3,7 @@ import Wysiwyg from "web_editor.wysiwyg"; import MassMailingSnippetsMenu from "mass_mailing.snippets.editor"; import {closestElement} from "@web_editor/js/editor/odoo-editor/src/OdooEditor"; +import Toolbar from "web_editor.toolbar"; const MassMailingWysiwyg = Wysiwyg.extend({ //-------------------------------------------------------------------------- @@ -17,15 +18,72 @@ const MassMailingWysiwyg = Wysiwyg.extend({ e.preventDefault(); } }); + this.snippetsMenuToolbar = this.toolbar; return res; }, toggleLinkTools(options = {}) { this._super({ ...options, - // Always open the dialog for the basic theme as it has no sidebar. - forceDialog: options.forceDialog || this.$iframeBody.find('.o_basic_theme').length + // Always open the dialog when the sidebar is folded. + forceDialog: options.forceDialog || this.snippetsMenu.folded }); + if (this.snippetsMenu.folded) { + // Hide toolbar and avoid it being re-displayed after getDeepRange. + this.odooEditor.document.getSelection().collapseToEnd(); + } + }, + + /** + * Sets SnippetsMenu fold state and switches toolbar. + * Instantiates a new floating Toolbar if needed. + * + * @param {Boolean} fold + */ + setSnippetsMenuFolded: async function (fold = true) { + if (fold) { + this.snippetsMenu.setFolded(true); + if (!this.floatingToolbar) { + // Instantiate and configure new toolbar. + this.floatingToolbar = new Toolbar(this, 'web_editor.toolbar'); + this.toolbar = this.floatingToolbar; + await this.toolbar.appendTo(document.createElement('void')); + this._configureToolbar({ snippets: false }); + this._updateEditorUI(); + this.setCSSVariables(this.toolbar.el); + this.odooEditor.setupToolbar(this.toolbar.el); + if (this.odooEditor.isMobile) { + document.body.querySelector('.o_mail_body').prepend(this.toolbar.el); + } else { + document.body.append(this.toolbar.el); + } + } else { + this.toolbar = this.floatingToolbar; + } + this.toolbar.el.classList.remove('d-none'); + this.odooEditor.autohideToolbar = true; + this.odooEditor.toolbarHide(); + } else { + this.snippetsMenu.setFolded(false); + this.toolbar = this.snippetsMenuToolbar; + this.odooEditor.autohideToolbar = false; + if (this.floatingToolbar) { + this.floatingToolbar.el.classList.add('d-none'); + } + } + this.odooEditor.toolbar = this.toolbar.el; + }, + + /** + * @override + */ + openMediaDialog: function() { + this._super(...arguments); + // Opening the dialog in the outer document does not trigger the selectionChange + // (that would normally hide the toolbar) in the iframe. + if (this.snippetsMenu.folded) { + this.odooEditor.toolbarHide(); + } }, //-------------------------------------------------------------------------- diff --git a/addons/mass_mailing/static/src/scss/mass_mailing.ui.scss b/addons/mass_mailing/static/src/scss/mass_mailing.ui.scss index 066439d3ba9..f91189c74f9 100644 --- a/addons/mass_mailing/static/src/scss/mass_mailing.ui.scss +++ b/addons/mass_mailing/static/src/scss/mass_mailing.ui.scss @@ -302,10 +302,6 @@ body.editor_enable.o_basic_theme.o_in_iframe { #web_editor-top-edit .note-popover .popover { right: 0 !important; } - - #oe_snippets { - display: none; - } } .note-editable .o_layout { diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js index 1d6cc50cd42..8a7d6d04d2b 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js @@ -627,30 +627,7 @@ export class OdooEditor extends EventTarget { // ------- if (this.options.toolbar) { - this.toolbar = this.options.toolbar; - this.bindExecCommand(this.toolbar); - // Ensure anchors in the toolbar don't trigger a hash change. - const toolbarAnchors = this.toolbar.querySelectorAll('a'); - toolbarAnchors.forEach(a => a.addEventListener('click', e => e.preventDefault())); - for (const colorLabel of this.toolbar.querySelectorAll('label')) { - colorLabel.addEventListener('mousedown', ev => { - // Hack to prevent loss of focus (done by preventDefault) while still opening - // color picker dialog (which is also prevented by preventDefault on chrome, - // except when click detail is 2, which happens on a double-click but isn't - // triggered by a dblclick event) - if (ev.detail < 2) { - ev.preventDefault(); - ev.currentTarget.dispatchEvent(new MouseEvent('click', { detail: 2 })); - } - }); - colorLabel.addEventListener('input', ev => { - this.document.execCommand(ev.target.name, false, ev.target.value); - this.updateColorpickerLabels(); - }); - } - if (this.isMobile) { - this.editable.before(this.toolbar); - } + this.setupToolbar(this.options.toolbar); } // placeholder hint if (editable.textContent === '' && this.options.placeholder) { @@ -677,6 +654,31 @@ export class OdooEditor extends EventTarget { this._columnUi.remove(); } + setupToolbar(toolbar) { + this.toolbar = toolbar; + this.autohideToolbar = this.options.autohideToolbar; + this.bindExecCommand(this.toolbar); + // Ensure anchors in the toolbar don't trigger a hash change. + const toolbarAnchors = this.toolbar.querySelectorAll('a'); + toolbarAnchors.forEach(a => a.addEventListener('click', e => e.preventDefault())); + for (const colorLabel of this.toolbar.querySelectorAll('label')) { + colorLabel.addEventListener('mousedown', ev => { + // Hack to prevent loss of focus (done by preventDefault) while still opening + // color picker dialog (which is also prevented by preventDefault on chrome, + // except when click detail is 2, which happens on a double-click but isn't + // triggered by a dblclick event) + if (ev.detail < 2) { + ev.preventDefault(); + ev.currentTarget.dispatchEvent(new MouseEvent('click', { detail: 2 })); + } + }); + colorLabel.addEventListener('input', ev => { + this.document.execCommand(ev.target.name, false, ev.target.value); + this.updateColorpickerLabels(); + }); + } + } + resetContent(value) { value = value || '


'; this.editable.innerHTML = value; @@ -2731,8 +2733,10 @@ export class OdooEditor extends EventTarget { * @param {boolean} [show] */ _updateToolbar(show) { - if (!this.options.toolbar) return; - if (!this.options.autohideToolbar && this.toolbar.style.visibility !== 'visible') { + if (!this.toolbar) { + return; + } + if (!this.autohideToolbar && this.toolbar.style.visibility !== 'visible') { this.toolbar.style.visibility = 'visible'; } @@ -2751,7 +2755,7 @@ export class OdooEditor extends EventTarget { } } } - if (this.options.autohideToolbar && !this.toolbar.contains(sel.anchorNode)) { + if (this.autohideToolbar && !this.toolbar.contains(sel.anchorNode)) { if (!this.isMobile) { if (show !== undefined) { this.toolbar.style.visibility = show ? 'visible' : 'hidden'; @@ -2883,13 +2887,13 @@ export class OdooEditor extends EventTarget { undoButton && undoButton.classList.toggle('disabled', !this.historyCanUndo()); const redoButton = this.toolbar.querySelector('#redo'); redoButton && redoButton.classList.toggle('disabled', !this.historyCanRedo()); - if (this.options.autohideToolbar && !this.isMobile && !this.toolbar.contains(sel.anchorNode)) { + if (this.autohideToolbar && !this.isMobile && !this.toolbar.contains(sel.anchorNode)) { this._positionToolbar(); } } updateToolbarPosition() { if ( - this.options.autohideToolbar && + this.autohideToolbar && !this.isMobile && getComputedStyle(this.toolbar).visibility === 'visible' ) { 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 36e2d8b26cf..7e4de61c2e3 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -1850,12 +1850,9 @@ var SnippetsMenu = Widget.extend({ this.$el = this.window.$(this.$el); this.$el.data('snippetMenu', this); - this.folded = !!this.options.foldSnippets; - this.customizePanel = document.createElement('div'); this.customizePanel.classList.add('o_we_customize_panel', 'd-none'); - // adds toolbar if not folded - this.setFolded(this.folded); + this._addToolbar(); this._checkEditorToolbarVisibilityCallback = this._checkEditorToolbarVisibility.bind(this); $(this.options.wysiwyg.odooEditor.document.body).on('click', this._checkEditorToolbarVisibilityCallback); @@ -2152,8 +2149,6 @@ var SnippetsMenu = Widget.extend({ this.el.classList.toggle('d-none', foldState); this.el.ownerDocument.body.classList.toggle('editor_has_snippets', !foldState); this.folded = !!foldState; - // "add" toolbar to set it inside the snippet menu/in the body - this._addToolbar(); }, /** * Get the editable area. @@ -4157,7 +4152,6 @@ var SnippetsMenu = Widget.extend({ }, _addToolbar(toolbarMode = "text") { if (this.folded) { - this._addToolbarToOriginalPosition(); return; } let titleText = _t("Inline Text"); @@ -4174,35 +4168,24 @@ var SnippetsMenu = Widget.extend({ } this.options.wysiwyg.toolbar.el.classList.remove('oe-floating'); - if (!this._$toolbarContainer) { - // Create toolbar custom container. - this._$toolbarContainer = $(''); - const $title = $("" + titleText + ""); - this._$toolbarContainer.append($title); - this._$toolbarContainer.append(this.options.wysiwyg.toolbar.$el); - $(this.customizePanel).append(this._$toolbarContainer); - // Create table-options custom container. - const $customizeTableBlock = $(QWeb.render('web_editor.toolbar.table-options')); - this.options.wysiwyg.odooEditor.bindExecCommand($customizeTableBlock[0]); - $(this.customizePanel).append($customizeTableBlock); - this._$removeFormatButton = this.options.wysiwyg.toolbar.$el.find('#removeFormat'); - $title.append(this._$removeFormatButton); - this._$toolbarContainer.append(this.options.wysiwyg.toolbar.$el); - } + // Create toolbar custom container. + this._$toolbarContainer = $(''); + const $title = $("" + titleText + ""); + this._$toolbarContainer.append($title); + this._$toolbarContainer.append(this.options.wysiwyg.toolbar.$el); + $(this.customizePanel).append(this._$toolbarContainer); + + // Create table-options custom container. + const $customizeTableBlock = $(QWeb.render('web_editor.toolbar.table-options')); + this.options.wysiwyg.odooEditor.bindExecCommand($customizeTableBlock[0]); + $(this.customizePanel).append($customizeTableBlock); + this._$removeFormatButton = this.options.wysiwyg.toolbar.$el.find('#removeFormat'); + $title.append(this._$removeFormatButton); + this._$toolbarContainer.append(this.options.wysiwyg.toolbar.$el); this._checkEditorToolbarVisibility(); }, - _addToolbarToOriginalPosition: function () { - const toolbar = this.options.wysiwyg.toolbar.el; - toolbar.classList.add('oe-floating'); - if (this.options.wysiwyg.odooEditor.isMobile) { - const editorEditable = this.options.wysiwyg.odooEditor.editable; - editorEditable.before(toolbar); - } else if (this.options.autohideToolbar) { - document.body.appendChild(toolbar); - } - }, /** * Update editor UI visibility based on the current range. */ diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js index 8cfc03f0a4a..320d9738bce 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js @@ -1516,7 +1516,7 @@ const Wysiwyg = Widget.extend({ break; } }; - if (!this.options.snippets) { + if (!options.snippets) { $toolbar.find('#justify, #media-insert').remove(); } $toolbar.find('#media-insert, #media-replace, #media-description').click(openTools); @@ -1874,7 +1874,7 @@ const Wysiwyg = Widget.extend({ ].join(',')).toggleClass('d-none', !isInMedia); // The image replace button is in the image options when the sidebar // exists. - if (this.snippetsMenu && $target.is('img')) { + if (this.snippetsMenu && !this.snippetsMenu.folded && $target.is('img')) { this.toolbar.$el.find('#media-replace').toggleClass('d-none', true); } // Only show the image-transform, image-crop and media-description @@ -1931,7 +1931,7 @@ const Wysiwyg = Widget.extend({ // Always hide the unlink button on media. this.toolbar.$el.find('#unlink').toggleClass('d-none', true); // Show the floatingtoolbar on the topleft of the media. - if (this.options.autohideToolbar) { + if (this.odooEditor.autohideToolbar && !this.odooEditor.isMobile) { const imagePosition = this.lastMediaClicked.getBoundingClientRect(); this.toolbar.$el.css({ visibility: 'visible', @@ -2461,7 +2461,7 @@ const Wysiwyg = Widget.extend({ } }, _onSelectionChange() { - if (this.options.autohideToolbar) { + if (this.odooEditor.autohideToolbar) { const isVisible = this.linkPopover && this.linkPopover.el.offsetParent; if (isVisible && !this.odooEditor.document.getSelection().isCollapsed) { this.linkPopover.hide();