[IMP] mass_mailing: restyle the mass mailing form view to full width
RATIONALE Currently, editing a mail feels a little claustrophobic because of the boxes within boxes within boxes design. Purpose of this task is to improve layout of mailing form view when being in Email Marketing, notably displaying editor in full width. SPECIFICATIONS In this task we remove the chatter and the sheet from the form view so that the mail editor takes up more horizontal space. The chatter is then moved into one of the notebook's tabs. Mass mailing form view currently has a scrollbar for the contents of the editor: one for the form view itself and one for the sidebar. In consequence we sometimes end up with three scrollbars side by side, which is ugly and confusing. In this task we ensure the height of the iframe is always the same as its contents so there is no need for a scrollbar. Note that SMS Marketing is untouched as it keeps the old layout. Indeed there is no need of such changes as it does not use the HTML editor. Actions that are generic (systray, list view, ...) also use the old display.. LINKS Task-2545445 closes odoo/odoo#71511 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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': [
|
||||
|
||||
@@ -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);
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Main form view for inheriting from -->
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_form">
|
||||
<field name="name">mailing.mailing.form</field>
|
||||
<field name="model">mailing.mailing</field>
|
||||
@@ -65,7 +66,7 @@
|
||||
<field name="state" readonly="1" widget="statusbar"/>
|
||||
</header>
|
||||
<div class="alert alert-info text-center" role="alert" attrs="{'invisible': ['&','&','&','&',('state', '!=', 'in_queue'),('sent', '=', 0),('ignored', '=', 0),('scheduled', '=', 0),('failed', '=', 0)]}">
|
||||
<div attrs="{'invisible': [('ignored', '=', 0)]}">
|
||||
<div class="o_mails_ignored" attrs="{'invisible': [('ignored', '=', 0)]}">
|
||||
<button class="btn-link py-0"
|
||||
name="action_view_traces_ignored"
|
||||
type="object">
|
||||
@@ -75,7 +76,7 @@
|
||||
</strong>
|
||||
</button>
|
||||
</div>
|
||||
<div attrs="{'invisible': [('scheduled', '=', 0)]}">
|
||||
<div class="o_mails_scheduled" attrs="{'invisible': [('scheduled', '=', 0)]}">
|
||||
<button class="btn-link py-0"
|
||||
name="action_view_traces_scheduled"
|
||||
type="object">
|
||||
@@ -85,7 +86,7 @@
|
||||
</strong>
|
||||
</button>
|
||||
</div>
|
||||
<div attrs="{'invisible': ['&', ('sent', '=', 0), ('state', 'in', ('draft', 'test', 'in_queue'))]}">
|
||||
<div class="o_mails_sent" attrs="{'invisible': ['&', ('sent', '=', 0), ('state', 'in', ('draft', 'test', 'in_queue'))]}">
|
||||
<button class="btn-link py-0"
|
||||
name="action_view_traces_sent"
|
||||
type="object">
|
||||
@@ -95,7 +96,7 @@
|
||||
</strong>
|
||||
</button>
|
||||
</div>
|
||||
<div attrs="{'invisible': ['|', ('state', '!=', 'done'), ('failed', '=', 0)]}">
|
||||
<div class="o_mails_failed" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('failed', '=', 0)]}">
|
||||
<button class="btn-link py-0"
|
||||
name="action_view_traces_failed"
|
||||
type="object">
|
||||
@@ -105,7 +106,7 @@
|
||||
</strong>
|
||||
</button>
|
||||
</div>
|
||||
<div attrs="{'invisible': [('state', '!=', 'in_queue')]}">
|
||||
<div class="o_mails_in_queue" attrs="{'invisible': [('state', '!=', 'in_queue')]}">
|
||||
<strong>
|
||||
<span name="next_departure_text">This mailing is scheduled for </span>
|
||||
<field name="next_departure" class="oe_inline"/>.
|
||||
@@ -315,6 +316,80 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Inherited form view for mass mailing's form specifically -->
|
||||
<record model="ir.ui.view" id="mailing_mailing_view_form_full_width">
|
||||
<field name="name">mailing.mailing.view.form.full.width</field>
|
||||
<field name="inherit_id" ref="mass_mailing.view_mail_mass_mailing_form"/>
|
||||
<field name="mode">primary</field>
|
||||
<field name="model">mailing.mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//form" position="attributes">
|
||||
<attribute name="js_class">mailing_mailing_view_form_full_width</attribute>
|
||||
<attribute name="class">o_mass_mailing_mailing_form o_mass_mailing_form_full_width</attribute>
|
||||
</xpath>
|
||||
<field name="state" position="before">
|
||||
<xpath expr="//div[hasclass('alert-info')]" position="move"/>
|
||||
</field>
|
||||
<xpath expr="//div[hasclass('alert-info')]" position="attributes">
|
||||
<attribute name="attrs">{'invisible': ['|',('state', '=', 'draft'),'&','&',('state', '!=', 'in_queue'),('scheduled', '=', 0),('failed', '=', 0)]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('alert-info')]/div[hasclass('o_mails_ignored')]" position="replace"/>
|
||||
<xpath expr="//div[hasclass('alert-info')]/div[hasclass('o_mails_sent')]" position="replace"/>
|
||||
<xpath expr="//div[hasclass('alert-info')]/div[hasclass('o_mails_failed')]//span[@name='failed_text']" position="replace">
|
||||
<span name="failed_text">email(s) not sent.</span>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('alert-info')]/div[hasclass('o_mails_scheduled')]" position="attributes">
|
||||
<attribute name="attrs">{'invisible': [('state', '!=', 'in_queue')]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('alert-info')]/div[hasclass('o_mails_scheduled')]/button" position="attributes">
|
||||
<attribute name="attrs">{'invisible': [('scheduled', '=', 0)]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('alert-info')]/div[hasclass('o_mails_scheduled')]//span[@name='scheduled_text']" position="replace">
|
||||
<span name="scheduled_text">email(s) scheduled for </span>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('alert-info')]/div[hasclass('o_mails_scheduled')]/button" position="after">
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('alert-info')]/div[hasclass('o_mails_in_queue')]/strong/span[@name='next_departure_text']" position="attributes">
|
||||
<attribute name="attrs">{'invisible': [('scheduled', '!=', 0)]}</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('alert-info')]/div[hasclass('o_mails_scheduled')]" position="inside">
|
||||
<xpath expr="//div[hasclass('alert-info')]/div[hasclass('o_mails_in_queue')]/strong" position="move"/>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('alert-info')]/div[hasclass('o_mails_in_queue')]" position="replace"/>
|
||||
<xpath expr="//sheet" position="before">
|
||||
<xpath expr="//div[hasclass('oe_button_box')]" position="move"/>
|
||||
<xpath expr="//widget[@name='web_ribbon']" position="move"/>
|
||||
<xpath expr="//group[hasclass('o_mass_mailing_mailing_group')]" position="move"/>
|
||||
<xpath expr="//notebook" position="move"/>
|
||||
</xpath>
|
||||
<xpath expr="//div[hasclass('oe_button_box')]" position="inside">
|
||||
<button name="action_view_traces_sent"
|
||||
attrs="{'invisible': [('state', 'in', ('draft','test'))]}"
|
||||
type="object" class="oe_stat_button" icon="fa-paper-plane">
|
||||
<field name="sent" widget="statinfo" string="Sent"/>
|
||||
</button>
|
||||
<button name="action_view_traces_ignored"
|
||||
attrs="{'invisible': [('state', 'in', ('draft','test'))]}"
|
||||
type="object" class="oe_stat_button" icon="fa-paper-plane-o">
|
||||
<field name="ignored" widget="statinfo" string="Ignored"/>
|
||||
</button>
|
||||
</xpath>
|
||||
<xpath expr="//notebook" position="inside">
|
||||
<page string="Chat" name="chat"/>
|
||||
</xpath>
|
||||
<xpath expr="//notebook/page[@name='chat']" position="inside">
|
||||
<xpath expr="//div[hasclass('oe_chatter')]" position="move"/>
|
||||
</xpath>
|
||||
<xpath expr="//notebook/page[@name='dynamic_placeholder_generator']" position="inside">
|
||||
<group/>
|
||||
</xpath>
|
||||
<xpath expr="//notebook/page[@name='dynamic_placeholder_generator']/group[2]" position="inside">
|
||||
<xpath expr="//notebook/page[@name='dynamic_placeholder_generator']/group[1]" position="move"/>
|
||||
</xpath>
|
||||
<xpath expr="//sheet" position="replace"/>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_kanban">
|
||||
<field name="name">mailing.mailing.kanban</field>
|
||||
<field name="model">mailing.mailing</field>
|
||||
@@ -475,6 +550,33 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mailing_mailing_action_mail_fullwidth_tree" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="0"/>
|
||||
<field name="view_mode">tree</field>
|
||||
<field name="act_window_id" ref="mass_mailing.mailing_mailing_action_mail"/>
|
||||
</record>
|
||||
<record id="mailing_mailing_action_mail_fullwidth_kanban" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="1"/>
|
||||
<field name="view_mode">kanban</field>
|
||||
<field name="act_window_id" ref="mass_mailing.mailing_mailing_action_mail"/>
|
||||
</record>
|
||||
<record id="mailing_mailing_action_mail_fullwidth_form" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="2"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="mass_mailing.mailing_mailing_view_form_full_width"/>
|
||||
<field name="act_window_id" ref="mass_mailing.mailing_mailing_action_mail"/>
|
||||
</record>
|
||||
<record id="mailing_mailing_action_mail_fullwidth_calendar" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="3"/>
|
||||
<field name="view_mode">calendar</field>
|
||||
<field name="act_window_id" ref="mass_mailing.mailing_mailing_action_mail"/>
|
||||
</record>
|
||||
<record id="mailing_mailing_action_mail_fullwidth_graph" model="ir.actions.act_window.view">
|
||||
<field name="sequence" eval="4"/>
|
||||
<field name="view_mode">graph</field>
|
||||
<field name="act_window_id" ref="mass_mailing.mailing_mailing_action_mail"/>
|
||||
</record>
|
||||
|
||||
<record id="action_view_mass_mailings_from_campaign" model="ir.actions.act_window">
|
||||
<field name="name">Mailings</field>
|
||||
<field name="res_model">mailing.mailing</field>
|
||||
|
||||
@@ -545,11 +545,17 @@ var dom = {
|
||||
* @param {number} [options.forcedOffset]
|
||||
* offset used instead of the automatic one (extraOffset will be
|
||||
* ignored too)
|
||||
* @param {JQuery} [options.$scrollable] the $element to scroll
|
||||
* @return {Promise}
|
||||
*/
|
||||
scrollTo(el, options = {}) {
|
||||
const $el = $(el);
|
||||
const $scrollable = $el.parent().closestScrollable();
|
||||
const $scrollable = options.$scrollable || $el.parent().closestScrollable();
|
||||
// If $scrollable and $el are not in the same document, we can safely
|
||||
// assume $el is in an $iframe. We retrieve it by filtering the list of
|
||||
// iframes in $scrollable to keep only the one that contains $el.
|
||||
const isInOneDocument = $scrollable[0].ownerDocument === $el[0].ownerDocument;
|
||||
const $iframe = !isInOneDocument && $scrollable.find('iframe').filter((i, node) => $(node).contents().has($el));
|
||||
const $topLevelScrollable = $().getScrollingElement();
|
||||
const isTopScroll = $scrollable.is($topLevelScrollable);
|
||||
|
||||
@@ -560,7 +566,10 @@ var dom = {
|
||||
offsetTop = $el.offset().top;
|
||||
el.classList.add('d-none');
|
||||
}
|
||||
const elPosition = $scrollable[0].scrollTop + (offsetTop - $scrollable.offset().top);
|
||||
let elPosition = $scrollable[0].scrollTop + (offsetTop - $scrollable.offset().top);
|
||||
if (!isInOneDocument && $iframe.length) {
|
||||
elPosition += $iframe.offset().top;
|
||||
}
|
||||
let offset = options.forcedOffset;
|
||||
if (offset === undefined) {
|
||||
offset = (isTopScroll ? dom.scrollFixedOffset() : 0) + (options.extraOffset || 0);
|
||||
|
||||
@@ -209,7 +209,7 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
|
||||
*/
|
||||
_toggleCodeView: function ($codeview, $codeviewButton) {
|
||||
this.wysiwyg.odooEditor.observerUnactive();
|
||||
$codeview.height(this.$content.height())
|
||||
$codeview.height(this.$content.height());
|
||||
$codeview.toggleClass('d-none');
|
||||
this.$content.toggleClass('d-none');
|
||||
if ($codeview.hasClass('d-none')) {
|
||||
@@ -309,6 +309,7 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, {
|
||||
}
|
||||
self.$content = $('#iframe_target', self.$iframe[0].contentWindow.document.body);
|
||||
resolver();
|
||||
self.trigger_up('iframe_updated', { $iframe: self.$iframe });
|
||||
};
|
||||
|
||||
this.$iframe.on('load', function onLoad() {
|
||||
|
||||
@@ -161,6 +161,9 @@ var SnippetEditor = Widget.extend({
|
||||
this.isTargetParentEditable = false;
|
||||
this.isTargetMovable = false;
|
||||
this.$scrollingElement = $().getScrollingElement(this.ownerDocument);
|
||||
if (!this.$scrollingElement[0]) {
|
||||
this.$scrollingElement = $(this.ownerDocument).find('.o_editable');
|
||||
}
|
||||
this.displayOverlayOptions = false;
|
||||
|
||||
this.__isStarted = new Promise(resolve => {
|
||||
@@ -209,7 +212,10 @@ var SnippetEditor = Widget.extend({
|
||||
},
|
||||
},
|
||||
});
|
||||
this.draggableComponent = new SmoothScrollOnDrag(this, this.$el, $().getScrollingElement(this.ownerDocument), smoothScrollOptions);
|
||||
const $scrollable = (this.options.wysiwyg.snippetsMenu && this.options.wysiwyg.snippetsMenu.$scrollable)
|
||||
|| (this.$scrollingElement.length && this.$scrollingElement)
|
||||
|| $().getScrollingElement(this.ownerDocument);
|
||||
this.draggableComponent = new SmoothScrollOnDrag(this, this.$el, $scrollable, smoothScrollOptions);
|
||||
} else {
|
||||
this.$('.o_overlay_move_options').addClass('d-none');
|
||||
$customize.find('.oe_snippet_clone').addClass('d-none');
|
||||
@@ -329,8 +335,8 @@ var SnippetEditor = Widget.extend({
|
||||
// be outside of the viewport (the whole header during an effect for
|
||||
// example).
|
||||
const rect = targetEl.getBoundingClientRect();
|
||||
const vpWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
const vpHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
const vpWidth = targetEl.ownerDocument.defaultView.innerWidth || document.documentElement.clientWidth;
|
||||
const vpHeight = targetEl.ownerDocument.defaultView.innerHeight || document.documentElement.clientHeight;
|
||||
const isInViewport = (
|
||||
rect.bottom > -0.1 &&
|
||||
rect.right > -0.1 &&
|
||||
@@ -831,7 +837,9 @@ var SnippetEditor = Widget.extend({
|
||||
|
||||
// If a modal is open, the scroll target must be that modal
|
||||
const $openModal = self.$editable.find('.modal:visible');
|
||||
self.draggableComponent.$scrollTarget = $openModal.length ? $openModal : self.$scrollingElement;
|
||||
if ($openModal.length) {
|
||||
self.draggableComponent.$scrollTarget = $openModal;
|
||||
}
|
||||
|
||||
// Trigger a scroll on the draggable element so that jQuery updates
|
||||
// the position of the drop zones.
|
||||
@@ -1294,6 +1302,9 @@ var SnippetsMenu = Widget.extend({
|
||||
// Hide the active overlay when scrolling.
|
||||
// Show it again and recompute all the overlays after the scroll.
|
||||
this.$scrollingElement = $().getScrollingElement(this.ownerDocument);
|
||||
if (!this.$scrollingElement[0]) {
|
||||
this.$scrollingElement = $(this.ownerDocument).find('.o_editable');
|
||||
}
|
||||
this._onScrollingElementScroll = _.throttle(() => {
|
||||
for (const editor of this.snippetEditors) {
|
||||
editor.toggleOverlayVisibility(false);
|
||||
@@ -2229,7 +2240,10 @@ var SnippetsMenu = Widget.extend({
|
||||
var $toInsert, dropped, $snippet;
|
||||
|
||||
let dragAndDropResolve;
|
||||
const $scrollingElement = $().getScrollingElement(this.ownerDocument);
|
||||
let $scrollingElement = $().getScrollingElement(this.ownerDocument);
|
||||
if (!$scrollingElement[0]) {
|
||||
$scrollingElement = $(this.ownerDocument).find('.o_editable');
|
||||
}
|
||||
|
||||
const smoothScrollOptions = this._getScrollOptions({
|
||||
jQueryDraggableOptions: {
|
||||
@@ -2306,7 +2320,9 @@ var SnippetsMenu = Widget.extend({
|
||||
|
||||
// If a modal is open, the scroll target must be that modal
|
||||
const $openModal = self.getEditableArea().find('.modal:visible');
|
||||
self.draggableComponent.$scrollTarget = $openModal.length ? $openModal : $scrollingElement;
|
||||
if ($openModal.length) {
|
||||
self.draggableComponent.$scrollTarget = $openModal;
|
||||
}
|
||||
|
||||
// Trigger a scroll on the draggable element so that jQuery updates
|
||||
// the position of the drop zones.
|
||||
@@ -2361,7 +2377,7 @@ var SnippetsMenu = Widget.extend({
|
||||
}
|
||||
|
||||
var $target = $toInsert;
|
||||
await self._scrollToSnippet($target);
|
||||
await self._scrollToSnippet($target, self.$scrollable);
|
||||
|
||||
_.defer(async function () {
|
||||
self._disableUndroppableSnippets();
|
||||
@@ -2446,10 +2462,11 @@ var SnippetsMenu = Widget.extend({
|
||||
*
|
||||
* @private
|
||||
* @param {jQuery} $el - snippet to scroll to
|
||||
* @param {jQuery} [$scrollable] - $element to scroll
|
||||
* @return {Promise}
|
||||
*/
|
||||
async _scrollToSnippet($el) {
|
||||
return dom.scrollTo($el[0], {extraOffset: 50});
|
||||
async _scrollToSnippet($el, $scrollable) {
|
||||
return dom.scrollTo($el[0], {extraOffset: 50, $scrollable: $scrollable});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
|
||||
@@ -123,9 +123,9 @@ Wysiwyg.include({
|
||||
const $codeview = $('<textarea class="o_codeview d-none"/>');
|
||||
self.$editable.addClass('o_editable oe_structure');
|
||||
|
||||
$iframeTarget.append($codeview);
|
||||
$iframeTarget.append($iframeWrapper);
|
||||
$iframeTarget.append($utilsZone);
|
||||
$iframeWrapper.append($codeview);
|
||||
$iframeWrapper.append(self.$editable);
|
||||
|
||||
self.options.toolbarHandler = $('#web_editor-top-edit', self.$iframe[0].contentWindow.document);
|
||||
|
||||
@@ -15,12 +15,15 @@ iframe.wysiwyg_iframe.o_fullscreen {
|
||||
|
||||
body.o_in_iframe {
|
||||
background-color: $o-view-background-color;
|
||||
display: flex;
|
||||
|
||||
.o_editable {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.note-editable {
|
||||
border: none;
|
||||
}
|
||||
|
||||
#oe_snippets {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body id="iframe_target" class="o_in_iframe">
|
||||
<body class="o_in_iframe">
|
||||
<div id="iframe_target"/>
|
||||
<script type="text/javascript">
|
||||
odoo.define('web_editor.wysiwyg.iniframe', function (require) {
|
||||
'use strict';
|
||||
|
||||
Reference in New Issue
Block a user