diff --git a/addons/mail/static/src/components/composer_text_input/composer_text_input.scss b/addons/mail/static/src/components/composer_text_input/composer_text_input.scss index 066364847bc..1c018420af6 100644 --- a/addons/mail/static/src/components/composer_text_input/composer_text_input.scss +++ b/addons/mail/static/src/components/composer_text_input/composer_text_input.scss @@ -17,6 +17,7 @@ .o_ComposerTextInput_textareaStyle { padding: 10px; // carefully crafted to have the text in the middle in chat window + min-height: 40px; resize: none; border-radius: $o-mail-rounded-rectangle-border-radius-lg; border: none; diff --git a/addons/mass_mailing/__manifest__.py b/addons/mass_mailing/__manifest__.py index cbb38dc6b81..ca6e6ecba0f 100644 --- a/addons/mass_mailing/__manifest__.py +++ b/addons/mass_mailing/__manifest__.py @@ -56,6 +56,7 @@ 'mass_mailing/static/src/css/email_template.css', 'mass_mailing/static/src/js/mass_mailing.js', 'mass_mailing/static/src/js/mass_mailing_widget.js', + 'mass_mailing/static/src/js/mailing_mailing_view_form_full_width.js', 'mass_mailing/static/src/js/unsubscribe.js', ], 'mass_mailing.assets_mail_themes': [ diff --git a/addons/mass_mailing/static/src/js/mailing_mailing_view_form_full_width.js b/addons/mass_mailing/static/src/js/mailing_mailing_view_form_full_width.js new file mode 100644 index 00000000000..c9b5eba6fc2 --- /dev/null +++ b/addons/mass_mailing/static/src/js/mailing_mailing_view_form_full_width.js @@ -0,0 +1,218 @@ +/** @odoo-module **/ + +import FormView from 'web.FormView'; +import FormController from 'web.FormController'; +import FormRenderer from 'web.FormRenderer'; +import { bus, _t } from 'web.core'; +import viewRegistry from 'web.view_registry'; +import config from 'web.config'; + +const MassMailingFullWidthFormController = FormController.extend({ + custom_events: _.extend({}, FormController.prototype.custom_events,{ + iframe_updated: '_onIframeUpdated', + }), + + /** + * @override + */ + init() { + this._super(...arguments); + this._boundOnDomUpdated = this._onDomUpdated.bind(this); + bus.on('DOM_updated', this, this._onDomUpdated); + this._resizeObserver = new ResizeObserver(this._onResizeIframeContents.bind(this)); + }, + /** + * @override + */ + destroy() { + bus.off('DOM_updated', this, this._boundOnDomUpdated); + this._super(...arguments); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Resize the mailing editor's iframe container so its height fits its + * contents. This needs to be called whenever the iframe's contents might + * have changed, eg. when adding/removing content to/from it or when a + * template is picked. + * + * @private + * @param {JQuery} $iframe + */ + _resizeMailingEditorIframe() { + const VERTICAL_OFFSET = 12; // Vertical offset picked for visual design purposes. + const minHeight = $(window).height() - Math.abs(this.$iframe.offset().top) - (VERTICAL_OFFSET / 2); + const $iframeDoc = this.$iframe.contents(); + const $themeSelectorNew = $iframeDoc.find('.o_mail_theme_selector_new'); + if ($themeSelectorNew.length) { + this.$iframe.height(Math.max($themeSelectorNew[0].scrollHeight + VERTICAL_OFFSET, minHeight)); + } else { + const ref = $iframeDoc.find('#iframe_target')[0]; + if (ref) { + this.$iframe.height(Math.max(ref.scrollHeight + VERTICAL_OFFSET, minHeight)); + } + } + }, + /** + * Reposition the sidebar so it always occupies the full available visible + * height, no matter the scroll position. This way, the sidebar is always + * visible and as big as possible. + * + * @private + */ + _repositionMailingEditorSidebar() { + const windowHeight = $(window).height(); + const $iframeDocument = this.$iframe.contents(); + const $sidebar = $iframeDocument.find('#oe_snippets'); + const isFullscreen = this._isFullScreen(); + if (isFullscreen) { + $sidebar.height(windowHeight); + this.$iframe.height(windowHeight); + $sidebar.css({ + top: '', + bottom: '', + }); + } else { + const iframeTop = this.$iframe.offset().top; + $sidebar.css({ + height: '', + top: Math.max(0, this.$('.o_content').offset().top - iframeTop), + bottom: this.$iframe.height() - windowHeight + iframeTop, + }); + } + }, + /** + * Return true if the mailing editor is in full screen mode, false + * otherwise. + * + * @private + * @returns {boolean} + */ + _isFullScreen() { + return window.top.document.body.classList.contains('o_field_widgetTextHtml_fullscreen'); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Assume the iframe was updated on each dom_updated event. + * + * @private + */ + _onDomUpdated() { + const data = { $iframe: this.$('iframe.wysiwyg_iframe, iframe.o_readonly') }; + this._onIframeUpdated({ data }); + }, + /** + * Resize the given iframe so its height fits its contents and initialize a + * resize observer to resize on each size change in its contents. + * This also ensures the contents of the sidebar remain visible no matter + * how much we resize the iframe and scroll down. + * + * @private + * @param {JQuery} ev.data.$iframe + */ + _onIframeUpdated(ev) { + const $iframe = ev.data.$iframe; + if (!$iframe.length || !$iframe.contents().length) { + return; + } + const hasIframeChanged = $iframe !== this.$iframe; + this.$iframe = $iframe; + this._resizeMailingEditorIframe(); + + const $iframeDoc = $iframe.contents(); + const iframeTarget = $iframeDoc.find('#iframe_target'); + if (hasIframeChanged) { + $iframeDoc.find('body').on('click', '.o_fullscreen_btn', this._onToggleFullscreen.bind(this)); + this.$('.o_content').on('scroll', this._repositionMailingEditorSidebar.bind(this)); + if (iframeTarget[0]) { + this._resizeObserver.disconnect(); + this._resizeObserver.observe(iframeTarget[0]); + } + } + if (iframeTarget[0]) { + const isFullscreen = this._isFullScreen(); + iframeTarget.css({ + display: isFullscreen ? '' : 'flex', + 'flex-direction': isFullscreen ? '' : 'column', + }); + } + }, + /** + * Switch "scrolling modes" on toggle fullscreen mode: in fullscreen mode, + * the scroll happens within the iframe whereas in regular mode we pretend + * there is no iframe and scroll in the top document. Also reposition the + * sidebar since toggling the fullscreen mode visibly changes the + * positioning of elements in the document. + * + * @private + */ + _onToggleFullscreen() { + const $iframeDoc = this.$iframe.contents(); + const iframeTarget = $iframeDoc.find('#iframe_target'); + const isFullscreen = this._isFullScreen(); + iframeTarget.css({ + display: isFullscreen ? '' : 'flex', + 'flex-direction': isFullscreen ? '' : 'column', + }); + const wysiwyg = $iframeDoc.find('.note-editable').data('wysiwyg'); + if (wysiwyg && wysiwyg.snippetsMenu) { + // Restore the appropriate scrollable depending on the mode. + this._$scrollable = this._$scrollable || wysiwyg.snippetsMenu.$scrollable; + wysiwyg.snippetsMenu.$scrollable = isFullscreen ? $iframeDoc.find('.note-editable') : this._$scrollable; + } + this._repositionMailingEditorSidebar(); + }, + /** + * Resize the iframe and reposition the sidebar whenever the contents of the + * iframe change height. + * + * @private + */ + _onResizeIframeContents() { + this._resizeMailingEditorIframe(); + this._repositionMailingEditorSidebar(); + }, +}); + +const MassMailingFullWidthFormRenderer = FormRenderer.extend({ + /** + * Overload the rendering of the header in order to add a child to it: move + * the alert after the statusbar. + * + * @private + * @override + */ + _renderTagHeader: function (node) { + const $statusbar = this._super(...arguments); + const alert = node.children.find(child => child.tag === "div" && child.attrs.role === "alert"); + const $alert = this._renderGenericTag(alert); + $statusbar.find('.o_statusbar_buttons').after($alert); + return $statusbar; + }, + /** + * Increase the default number of button boxes before folding since the form + * without sheet is a lot bigger and more space is available for them. + * + * @private + * @override + */ + _renderButtonBoxNbButtons: function () { + return [2, 2, 2, 4, 6, 7][config.device.size_class] || 10; + }, +}); + +export const MassMailingFullWidthFormView = FormView.extend({ + config: Object.assign({}, FormView.prototype.config, { + Controller: MassMailingFullWidthFormController, + Renderer: MassMailingFullWidthFormRenderer, + }), +}); + +viewRegistry.add('mailing_mailing_view_form_full_width', MassMailingFullWidthFormView); diff --git a/addons/mass_mailing/static/src/js/mass_mailing_widget.js b/addons/mass_mailing/static/src/js/mass_mailing_widget.js index fa760a51d97..2218c68ab12 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_widget.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_widget.js @@ -299,6 +299,20 @@ var MassMailingFieldHtml = FieldHtml.extend({ } } this.wysiwyg.trigger('reload_snippet_dropzones'); + this.trigger_up('iframe_updated', { $iframe: this.wysiwyg.$iframe }); + }, + + /** + * @private + * @override + */ + _toggleCodeView: function ($codeview) { + this._super(...arguments); + const isFullWidth = !!$(window.top.document).find('.o_mass_mailing_form_full_width')[0]; + $codeview.css('height', isFullWidth ? $(window).height() : ''); + if ($codeview.hasClass('d-none')) { + this.trigger_up('iframe_updated', { $iframe: this.wysiwyg.$iframe }); + } }, /** @@ -330,11 +344,8 @@ var MassMailingFieldHtml = FieldHtml.extend({ this._super(); this.wysiwyg.odooEditor.observerFlush(); this.wysiwyg.odooEditor.resetHistory(); - //this will make the editor take all the bottom space - this.wysiwyg.$iframe.css({ - 'min-height': '100vh', - width: '100%' - }); + this.wysiwyg.$iframeBody.addClass('o_mass_mailing_iframe'); + this.trigger_up('iframe_updated', { $iframe: this.wysiwyg.$iframe }); }, /** * @private @@ -352,6 +363,15 @@ var MassMailingFieldHtml = FieldHtml.extend({ */ _onSnippetsLoaded: function (ev) { var self = this; + if (this.wysiwyg.snippetsMenu && $(window.top.document).find('.o_mass_mailing_form_full_width')[0]) { + // In full width form mode, ensure the snippets menu's scrollable is + // in the form view, not in the iframe. + this.wysiwyg.snippetsMenu.$scrollable = this.$el.closestScrollable(); + // Ensure said scrollable keeps its scrollbar at all times to + // prevent the scrollbar from appearing at awkward moments (ie: when + // previewing an option) + this.wysiwyg.snippetsMenu.$scrollable.css('overflow-y', 'scroll'); + } if (!this.$content) { this.snippetsLoaded = ev; return; @@ -372,7 +392,7 @@ var MassMailingFieldHtml = FieldHtml.extend({ if (!odoo.debug) { $snippetsSideBar.find('.o_codeview_btn').hide(); } - const $codeview = this.$content.parent().find('textarea.o_codeview'); + const $codeview = this.wysiwyg.$iframe.contents().find('textarea.o_codeview'); $snippetsSideBar.on('click', '.o_codeview_btn', () => this._toggleCodeView($codeview)); if ($themes.length === 0) { @@ -430,8 +450,6 @@ var MassMailingFieldHtml = FieldHtml.extend({ $themeSelectorNew.appendTo(this.wysiwyg.$iframeBody); } - this.wysiwyg.$iframeBody.addClass("o_mass_mailing_iframe") - /** * Add proposition to install enterprise themes if not installed. */ diff --git a/addons/mass_mailing/static/src/scss/mass_mailing.scss b/addons/mass_mailing/static/src/scss/mass_mailing.scss index a035f5bd08c..ab88b06e15f 100644 --- a/addons/mass_mailing/static/src/scss/mass_mailing.scss +++ b/addons/mass_mailing/static/src/scss/mass_mailing.scss @@ -1,4 +1,4 @@ -.o_form_view .o_form_sheet .o_notebook .tab-content .tab-pane .o_mail_body { +.o_form_view.o_mass_mailing_mailing_form .o_notebook .tab-content .tab-pane .o_mail_body { // cancel the padding of the form_sheet margin-top: -$o-sheet-cancel-hpadding; margin-left: -$o-sheet-cancel-hpadding; @@ -14,6 +14,47 @@ } } +.o_form_view.o_mass_mailing_mailing_form.o_mass_mailing_form_full_width { + .o_form_statusbar { + margin-bottom: 0; + + .o_statusbar_status { + border-left: solid $o-gray-300 1px; + } + } + .o_mass_mailing_mailing_group { + margin-top: $o-sheet-cancel-tpadding + 33px; + } + .o_mail_add_emoji { + float: none; + display: inline-block; + bottom: 0; + } + input.o_field_char.o_field_widget { + min-width: 86%; + } + .o_FormRenderer_chatterContainer { + margin: 0; + max-width: unset; + width: 100%; + border: solid $o-gray-300 1px; + } + .wysiwyg_iframe { + border: none; + } + .oe_button_box { + margin-top: unset; + + > .btn.oe_stat_button { + max-width: 135px; + border-left: 1px solid $border-color; + } + > .o_dropdown_more { + width: 135px; + } + } +} + .o_kanban_view { .oe_kanban_mass_mailing { .o_title { @@ -40,17 +81,30 @@ } .o_form_view .o_group.o_inner_group.o_mass_mailing_mailing_group { - //used to gain extra space in form + // Used to gain extra space in form. display: block; } -.o_mass_mailing_mailing_form { +.o_mass_mailing_mailing_form:not(.o_mass_mailing_form_full_width) { .alert:not(.o_invisible_modifier) + .clearfix.position-relative.o_form_sheet { //hides extra space between form and alert messages margin-top: -12px; } } +.o_mass_mailing_mailing_form.o_mass_mailing_form_full_width .alert.alert-info:not(.o_invisible_modifier) { + margin: 0 0 0 12px; + flex: 1; + padding: unset; + border-left-width: 1px; + border-left-color: #dee2e6; + + > div { + min-height: 100%; + padding: 6px; + } +} + .o_form_view.o_xxl_form_view.o_mass_mailing_mailing_form { // This will hide the chatter scroll bar height: initial; diff --git a/addons/mass_mailing/views/mailing_mailing_views.xml b/addons/mass_mailing/views/mailing_mailing_views.xml index ce35869f943..5b9c3405fea 100644 --- a/addons/mass_mailing/views/mailing_mailing_views.xml +++ b/addons/mass_mailing/views/mailing_mailing_views.xml @@ -47,6 +47,7 @@ + mailing.mailing.form mailing.mailing @@ -65,7 +66,7 @@