From 05b9eff656a454138faadd30287db08f09dfa7b3 Mon Sep 17 00:00:00 2001 From: Rodolpho Lima Date: Wed, 17 May 2023 17:51:07 +0200 Subject: [PATCH] [FIX] web_editor: toolbar scroll over navbar Before this commit the floating toolbar would scroll over the headers above the html field, such as the form view header and the web client navbar. This commit ensures the toolbar is hidden when it overflows the scroll container. task-3263463 X-original-commit: 96d180c7591bcb359c5b74796f39c0e36ea541fa Part-of: odoo/odoo#133459 --- .../js/editor/odoo-editor/src/OdooEditor.js | 29 ++++++++++++------- .../static/src/js/wysiwyg/wysiwyg.js | 7 +++++ .../static/src/js/wysiwyg/wysiwyg_iframe.js | 7 ++++- 3 files changed, 31 insertions(+), 12 deletions(-) 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 8e299ab276c..ee68cd8acbb 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 @@ -216,6 +216,7 @@ export class OdooEditor extends EventTarget { getContextFromParentRect: () => { return { top: 0, left: 0 }; }, + getScrollContainerRect: () => document.body.getBoundingClientRect(), toSanitize: true, isRootEditable: true, placeholder: false, @@ -3070,7 +3071,8 @@ export class OdooEditor extends EventTarget { _positionToolbar() { const OFFSET = 10; let isBottom = false; - this.toolbar.classList.toggle('toolbar-bottom', false); + // Toolbar display must not be none in order to calculate width and height. + this.toolbar.classList.toggle('d-none', false); this.toolbar.style.maxWidth = window.innerWidth - OFFSET * 2 + 'px'; const sel = this.document.getSelection(); const range = sel.getRangeAt(0); @@ -3095,6 +3097,7 @@ export class OdooEditor extends EventTarget { const toolbarHeight = this.toolbar.offsetHeight; const editorRect = this.editable.getBoundingClientRect(); const parentContextRect = this.options.getContextFromParentRect(); + const scrollContainerRect = this.options.getScrollContainerRect(); const editorTopPos = Math.max(0, editorRect.top); const scrollX = document.defaultView.scrollX; const scrollY = document.defaultView.scrollY; @@ -3111,14 +3114,12 @@ export class OdooEditor extends EventTarget { // Get top position. let top = correctedSelectionRect.top - toolbarHeight - OFFSET; - // Ensure the toolbar doesn't overflow the editor on the top. - if (top < editorTopPos) { + // Ensure the toolbar doesn't overflow the editor or scroll container on the top. + if (top < editorTopPos || top + parentContextRect.top - scrollContainerRect.top < OFFSET / 2) { // Position the toolbar below the selection. top = correctedSelectionRect.bottom + OFFSET; isBottom = true; } - // Ensure the toolbar doesn't overflow the editor on the bottom. - top = Math.min(window.innerHeight - OFFSET - toolbarHeight, top); // Offset top to compensate for parent context position (eg. Iframe). top += parentContextRect.top; this.toolbar.style.top = scrollY + top + 'px'; @@ -3130,12 +3131,18 @@ export class OdooEditor extends EventTarget { // Ensure the arrow doesn't overflow the toolbar on the right. arrowLeftPos = Math.min(toolbarWidth - OFFSET - 20, arrowLeftPos); this.toolbar.style.setProperty('--arrow-left-pos', arrowLeftPos + 'px'); - if (isBottom) { - this.toolbar.classList.toggle('toolbar-bottom', true); - this.toolbar.style.setProperty('--arrow-top-pos', -17 + 'px'); - } else { - this.toolbar.style.setProperty('--arrow-top-pos', toolbarHeight - 3 + 'px'); - } + const arrowTopPos = isBottom ? -17 : toolbarHeight - 3; + this.toolbar.classList.toggle('toolbar-bottom', isBottom); + this.toolbar.style.setProperty('--arrow-top-pos', arrowTopPos + 'px'); + + // Calculate toolbar dimensions including the arrow. + const toolbarTop = Math.min(top , top + arrowTopPos); + const toolbarBottom = Math.max(top + toolbarHeight, top + arrowTopPos + 20); + + // Hide toolbar if it overflows the scroll container. + const distToScrollContainer = Math.min(toolbarTop - scrollContainerRect.top, + scrollContainerRect.bottom - toolbarBottom); + this.toolbar.classList.toggle('d-none', distToScrollContainer < OFFSET / 2); } // PASTING / DROPPING diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js index 63853bb2f28..ae413bc1872 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js @@ -368,6 +368,12 @@ export class Wysiwyg extends Component { allowInlineAtRoot: this.options.allowInlineAtRoot, getYoutubeVideoElement: getYoutubeVideoElement, getContextFromParentRect: options.getContextFromParentRect, + getScrollContainerRect: () => { + if (!this.scrollContainer || !this.scrollContainer.getBoundingClientRect) { + this.scrollContainer = document.querySelector('.o_action_manager'); + } + return this.scrollContainer.getBoundingClientRect(); + }, getPowerboxElement: () => { const selection = (this.options.document || document).getSelection(); if (selection.isCollapsed && selection.rangeCount) { @@ -2760,6 +2766,7 @@ export class Wysiwyg extends Component { } _onScroll(ev) { if (ev.target.contains(this.$editable[0])) { + this.scrollContainer = ev.target; this.odooEditor.updateToolbarPosition(); } } diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js index f6eef88e415..46e450ae622 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js @@ -204,7 +204,12 @@ patch(Wysiwyg.prototype, { 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) { + if (ev.target.contains(this.$iframe[0])) { + this.scrollContainer = ev.target; + this.odooEditor.updateToolbarPosition(); + } else if (scrollInIframe) { + // UpdateToolbarPosition needs a scroll container in the top document. + this.scrollContainer = this.$iframe[0]; this.odooEditor.updateToolbarPosition(); } } else {