[FIX] web_editor: toolbar position update on scroll

Before this commit the floating toolbar position was not being updated
on scroll events in certain scenarios. The problem resulted from adding
the event listener to the incorrect scroll container, which was
calculated only once in the Wysiwyg life cycle, on start.

The calculated scrollable container could be incorrect due to:
- changes in the web client resulting from the swich from read to edit
mode taking place after the Wysiwyg was started,
- changes in the web client resulting from a window resize (particularly
when, in the form view, the chatter switches position from right to
bottom and vice-versa),
- the editor being mounted inside an iframe and the scroll container
being an element in the top document.
- the editor being mounter inside an iframe and the scroll container
being the iframe's root element.

This commit solves the issue by detecting scroll events anywhere in the
document (and in the iframe's document when it applies) and updating the
toolbar position when the editable is a descendant of the scrolled
element.

task-3263463

X-original-commit: 5382c995eecfe2fcea39d9456860b48704645c99
Part-of: odoo/odoo#133459
This commit is contained in:
Rodolpho Lima
2023-08-30 13:10:22 +00:00
parent 2cff7e4094
commit ea2bcfc83c
2 changed files with 52 additions and 12 deletions
@@ -273,6 +273,7 @@ export class Wysiwyg extends Component {
this.colorpickers = {};
this._onDocumentMousedown = this._onDocumentMousedown.bind(this);
this._onBlur = this._onBlur.bind(this);
this._onScroll = this._onScroll.bind(this);
this.customizableLinksSelector = 'a'
+ ':not([data-bs-toggle="tab"])'
+ ':not([data-bs-toggle="collapse"])'
@@ -947,6 +948,7 @@ export class Wysiwyg extends Component {
for (const timeout of this.tooltipTimeouts) {
clearTimeout(timeout);
}
document.removeEventListener('scroll', this._onScroll, true);
}
/**
* @override
@@ -1919,18 +1921,10 @@ export class Wysiwyg extends Component {
}
this._updateFaResizeButtons();
});
// we need the Timeout to be sure the editable content is loaded
// before calculating the scrollParent() element.
setTimeout(() => {
const scrollableContainer = this.$el.scrollParent();
if (!options.snippets && scrollableContainer.length) {
this.odooEditor.addDomListener(
scrollableContainer[0],
'scroll',
this.odooEditor.updateToolbarPosition.bind(this.odooEditor),
);
}
}, 0);
if (!options.snippets) {
// Scroll event does not bubble.
document.addEventListener('scroll', this._onScroll, true);
}
}
/**
* @private
@@ -2764,6 +2758,11 @@ export class Wysiwyg extends Component {
this.options.onWysiwygBlur && this.options.onWysiwygBlur();
}
}
_onScroll(ev) {
if (ev.target.contains(this.$editable[0])) {
this.odooEditor.updateToolbarPosition();
}
}
_signalOffline() {
if (!this._isOnline) {
return;
@@ -38,6 +38,16 @@ patch(Wysiwyg.prototype, {
}
},
/**
* @override
**/
destroy() {
if (this.options.inIframe) {
this.$iframe?.[0].contentDocument.removeEventListener('scroll', this._onScroll, true);
}
super.destroy();
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
@@ -180,6 +190,37 @@ patch(Wysiwyg.prototype, {
this.$iframe[0].contentWindow.addEventListener('blur', this._onBlur);
}
},
/**
* When the editable is inside an iframe, we want to update the toolbar
* position in 2 scenarios:
* 1. scroll event in the top document, if the iframe is a descendant of
* the scroll container.
* 2. scroll event in the iframe's document.
*
* @override
*/
_onScroll(ev) {
if (this.options.inIframe) {
const iframeDocument = this.$iframe[0].contentDocument;
const scrollInIframe = ev.target === iframeDocument || ev.target.ownerDocument === iframeDocument;
if (ev.target.contains(this.$iframe[0]) || scrollInIframe) {
this.odooEditor.updateToolbarPosition();
}
} else {
return super._onScroll(...arguments);
}
},
/**
* @override
*/
_configureToolbar(options) {
super._configureToolbar(...arguments);
if (this.options.inIframe && !options.snippets) {
this.$iframe[0].contentDocument.addEventListener('scroll', this._onScroll, true);
}
},
});
function getWysiwygIframeContent(params) {