diff --git a/addons/account/static/src/js/account_portal_sidebar.js b/addons/account/static/src/js/account_portal_sidebar.js
index 0b48c0a9647..58114e00a56 100644
--- a/addons/account/static/src/js/account_portal_sidebar.js
+++ b/addons/account/static/src/js/account_portal_sidebar.js
@@ -1,6 +1,7 @@
odoo.define('account.AccountPortalSidebar', function (require) {
'use strict';
+const dom = require('web.dom');
var publicWidget = require('web.public.widget');
var PortalSidebar = require('portal.PortalSidebar');
var utils = require('web.utils');
@@ -57,7 +58,7 @@ publicWidget.registry.AccountPortalSidebar = PortalSidebar.extend({
if (!$target.length) {
return;
}
- $('html, body').scrollTop($target.offset().top);
+ dom.scrollTo($target[0], {duration: 0});
},
/**
* @private
diff --git a/addons/portal/static/src/js/portal_chatter.js b/addons/portal/static/src/js/portal_chatter.js
index a0a761f4d8c..134efbf6097 100644
--- a/addons/portal/static/src/js/portal_chatter.js
+++ b/addons/portal/static/src/js/portal_chatter.js
@@ -2,6 +2,7 @@ odoo.define('portal.chatter', function (require) {
'use strict';
var core = require('web.core');
+const dom = require('web.dom');
var publicWidget = require('web.public.widget');
var time = require('web.time');
var portalComposer = require('portal.composer');
@@ -292,17 +293,15 @@ publicWidget.registry.portalChatter = publicWidget.Widget.extend({
/**
* @override
*/
- start: function () {
- var self = this;
- var defs = [this._super.apply(this, arguments)];
- var chatter = new PortalChatter(this, this.$el.data());
- defs.push(chatter.appendTo(this.$el));
- return Promise.all(defs).then(function () {
- // scroll to the right place after chatter loaded
- if (window.location.hash === '#' + self.$el.attr('id')) {
- $('html, body').scrollTop(self.$el.offset().top);
- }
- });
+ async start() {
+ const proms = [this._super.apply(this, arguments)];
+ const chatter = new PortalChatter(this, this.$el.data());
+ proms.push(chatter.appendTo(this.$el));
+ await Promise.all(proms);
+ // scroll to the right place after chatter loaded
+ if (window.location.hash === `#${this.el.id}`) {
+ dom.scrollTo(this.el, {duration: 0});
+ }
},
});
diff --git a/addons/web/static/src/js/core/dom.js b/addons/web/static/src/js/core/dom.js
index 716593eb558..f4aba50be8a 100644
--- a/addons/web/static/src/js/core/dom.js
+++ b/addons/web/static/src/js/core/dom.js
@@ -141,6 +141,12 @@ var dom = {
});
}
},
+ /**
+ * @return {HTMLElement}
+ */
+ closestScrollable(el) {
+ return $(el).closestScrollable()[0];
+ },
/**
* jQuery find function behavior is::
*
@@ -228,6 +234,26 @@ var dom = {
}
return position;
},
+ /**
+ * @returns {HTMLElement}
+ */
+ getScrollingElement() {
+ return $().getScrollingElement()[0];
+ },
+ /**
+ * @param {HTMLElement} el
+ * @returns {boolean}
+ */
+ hasScrollableContent(el) {
+ return $(el).hasScrollableContent();
+ },
+ /**
+ * @param {HTMLElement} el
+ * @returns {boolean}
+ */
+ isScrollable(el) {
+ return $(el).isScrollable();
+ },
/**
* Protects a function which is to be used as a handler by preventing its
* execution for the duration of a previous call to it (including async
@@ -454,6 +480,81 @@ var dom = {
.select();
}
},
+ /**
+ * Computes the size by which a scrolling point should be decreased so that
+ * the top fixed elements of the page appear above that scrolling point.
+ *
+ * @returns {number}
+ */
+ scrollFixedOffset() {
+ let size = 0;
+ for (const el of $('.o_top_fixed_element')) {
+ size += $(el).outerHeight();
+ }
+ return size;
+ },
+ /**
+ * @param {HTMLElement} el - the element to stroll to
+ * @param {number} [options] - same as animate of jQuery
+ * @param {number} [options.extraOffset=0]
+ * extra offset to add on top of the automatic one (the automatic one
+ * being computed based on fixed header sizes)
+ * @param {number} [options.forcedOffset]
+ * offset used instead of the automatic one (extraOffset will be
+ * ignored too)
+ * @return {Promise}
+ */
+ scrollTo(el, options = {}) {
+ const $el = $(el);
+ const $scrollable = $el.parent().closestScrollable();
+ const $topLevelScrollable = $().getScrollingElement();
+ const isTopScroll = $scrollable.is($topLevelScrollable);
+
+ function _computeScrollTop() {
+ const elPosition = $scrollable[0].scrollTop + ($el.offset().top - $scrollable.offset().top);
+ let offset = options.forcedOffset;
+ if (offset === undefined) {
+ offset = (isTopScroll ? dom.scrollFixedOffset() : 0) + (options.extraOffset || 0);
+ }
+ return Math.max(0, elPosition - offset);
+ }
+
+ const originalScrollTop = _computeScrollTop();
+
+ return new Promise(resolve => {
+ const clonedOptions = Object.assign({}, options);
+
+ // During the animation, detect any change needed for the scroll
+ // offset. If any occurs, stop the animation and continuing it to
+ // the new scroll point for the remaining time.
+ // Note: limitation, the animation won't be as fluid as possible if
+ // the easing mode is different of 'linear'.
+ clonedOptions.progress = function (a, b, remainingMs) {
+ if (options.progress) {
+ options.progress.apply(this, ...arguments);
+ }
+ const newScrollTop = _computeScrollTop();
+ if (Math.abs(newScrollTop - originalScrollTop) <= 1.0) {
+ return;
+ }
+ $scrollable.stop();
+ dom.scrollTo(el, Object.assign({}, options, {
+ duration: remainingMs,
+ })).then(() => resolve());
+ };
+
+ // Detect the end of the animation to be able to indicate it to
+ // the caller via the returned Promise.
+ clonedOptions.complete = function () {
+ if (options.complete) {
+ options.complete.apply(this, ...arguments);
+ }
+ resolve();
+ };
+
+ $scrollable.animate({scrollTop: originalScrollTop}, clonedOptions);
+ });
+ },
/**
* Creates an automatic 'more' dropdown-menu for a set of navbar items.
*
diff --git a/addons/web/static/src/js/libs/bootstrap.js b/addons/web/static/src/js/libs/bootstrap.js
index 8f200794fa8..07e04c64a39 100644
--- a/addons/web/static/src/js/libs/bootstrap.js
+++ b/addons/web/static/src/js/libs/bootstrap.js
@@ -82,6 +82,20 @@ $.fn.tooltip.Constructor.prototype.show = function () {
return bootstrapShowFunction.call(this);
};
+/* Bootstrap scrollspy fix for non-body to spy */
+
+const bootstrapSpyRefreshFunction = $.fn.scrollspy.Constructor.prototype.refresh;
+$.fn.scrollspy.Constructor.prototype.refresh = function () {
+ bootstrapSpyRefreshFunction.apply(this, arguments);
+ if (this._scrollElement === window || this._config.method !== 'offset') {
+ return;
+ }
+ const baseScrollTop = this._getScrollTop();
+ for (let i = 0; i < this._offsets.length; i++) {
+ this._offsets[i] += baseScrollTop;
+ }
+};
+
return {
makeExtendedSanitizeWhiteList: makeExtendedSanitizeWhiteList,
};
diff --git a/addons/web/static/src/js/libs/jquery.js b/addons/web/static/src/js/libs/jquery.js
index 1a33d43c2e5..6921b004019 100644
--- a/addons/web/static/src/js/libs/jquery.js
+++ b/addons/web/static/src/js/libs/jquery.js
@@ -122,5 +122,92 @@ $.fn.extend({
});
});
},
+ /**
+ * @return {jQuery}
+ */
+ closestScrollable() {
+ let $el = this;
+ while ($el[0] !== document.scrollingElement) {
+ if ($el.isScrollable()) {
+ return $el;
+ }
+ $el = $el.parent();
+ }
+ return $el;
+ },
+ /**
+ * @returns {jQuery}
+ */
+ getScrollingElement() {
+ const $baseScrollingElement = $(document.scrollingElement);
+ if ($baseScrollingElement.isScrollable()
+ && $baseScrollingElement.hasScrollableContent()) {
+ return $baseScrollingElement;
+ }
+ const bodyHeight = $(document.body).height();
+ for (const el of document.body.children) {
+ // Search for a body child which is at least as tall as the body
+ // and which has the ability to scroll if enough content in it. If
+ // found, suppose this is the top scrolling element.
+ if (el.scrollHeight < bodyHeight) {
+ continue;
+ }
+ const $el = $(el);
+ if ($el.isScrollable()) {
+ return $el;
+ }
+ }
+ return $baseScrollingElement;
+ },
+ /**
+ * @return {boolean}
+ */
+ hasScrollableContent() {
+ return this[0].scrollHeight > this[0].clientHeight;
+ },
+ /**
+ * @returns {boolean}
+ */
+ isScrollable() {
+ const overflow = this.css('overflow-y');
+ return overflow === 'auto' || overflow === 'scroll'
+ || (overflow === 'visible' && this === document.scrollingElement);
+ },
});
+
+// jQuery functions monkey-patching
+
+// Some magic to ensure scrolltop and animate on html/body animate the top level
+// scrollable element even if not html or body.
+const originalScrollTop = $.fn.scrollTop;
+$.fn.scrollTop = function (value) {
+ if (value !== undefined && this.filter('html, body').length) {
+ // The caller wants to scroll a set of elements including html and/or
+ // body to a specific point -> do that but make sure to add the real
+ // top level element to that set of elements if any different is found.
+ originalScrollTop.apply(this.not('html, body').add($().getScrollingElement()), arguments);
+ return this;
+ } else if (value === undefined && this.eq(0).is('html, body')) {
+ // The caller wants to get the scroll point of a set of elements, jQuery
+ // will return the scroll point of the first one, if it is html or body
+ // return the scroll point of the real top level element.
+ return originalScrollTop.apply($().getScrollingElement(), arguments);
+ }
+ return originalScrollTop.apply(this, arguments);
+};
+const originalAnimate = $.fn.animate;
+$.fn.animate = function (properties, ...rest) {
+ const props = Object.assign({}, properties);
+ if ('scrollTop' in props && this.filter('html, body').length) {
+ // The caller wants to scroll a set of elements including html and/or
+ // body to a specific point -> do that but make sure to add the real
+ // top level element to that set of elements if any different is found.
+ originalAnimate.call(this.not('html, body').add($().getScrollingElement()), {'scrollTop': props['scrollTop']}, ...rest);
+ delete props['scrollTop'];
+ }
+ if (!Object.keys(props).length) {
+ return this;
+ }
+ return originalAnimate.call(this, props, ...rest);
+};
});
diff --git a/addons/web/static/src/scss/base_frontend.scss b/addons/web/static/src/scss/base_frontend.scss
index f1c341a89f9..585ce965ec5 100644
--- a/addons/web/static/src/scss/base_frontend.scss
+++ b/addons/web/static/src/scss/base_frontend.scss
@@ -1,4 +1,9 @@
// Frontend general
+html, body, #wrapwrap {
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
#wrapwrap {
// The z-index is useful to prevent that children with a negative z-index
// go behind the wrapwrap (we create a new stacking context).
@@ -6,8 +11,11 @@
position: relative;
display: flex;
flex-flow: column nowrap;
- width: 100%;
- min-height: 100%;
+ // ... we delegate the scroll to that top element instead of the window/body
+ // This is at least needed for the edit mode to not have a double scrollbar
+ // due to the right editor panel (and since we want to minimize the style
+ // difference between edit mode and non-edit mode (wysiwyg)...).
+ overflow: auto;
> * {
flex: 0 0 auto;
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 aa5bdd867e9..ecebf152f9d 100644
--- a/addons/web_editor/static/src/js/editor/snippets.editor.js
+++ b/addons/web_editor/static/src/js/editor/snippets.editor.js
@@ -100,7 +100,7 @@ var SnippetEditor = Widget.extend({
},
},
});
- this.draggableComponent = new SmoothScrollOnDrag(this, this.$el, $('html'), smoothScrollOptions);
+ this.draggableComponent = new SmoothScrollOnDrag(this, this.$el, $().getScrollingElement(), smoothScrollOptions);
} else {
this.$('.o_overlay_move_options').addClass('d-none');
$customize.find('.oe_snippet_clone').addClass('d-none');
@@ -997,8 +997,8 @@ var SnippetsMenu = Widget.extend({
// Hide the active overlay when scrolling.
// Show it again and recompute all the overlays after the scroll.
- // TODO: current limitation, suppose that this is the body which scrolls
- this.$document.on('scroll.snippets_menu', _.throttle(() => {
+ this.$scrollingElement = $().getScrollingElement();
+ this.$scrollingElement.on('scroll.snippets_menu', _.throttle(() => {
for (const editor of this.snippetEditors) {
editor.toggleOverlayVisibility(false);
}
@@ -1054,6 +1054,7 @@ var SnippetsMenu = Widget.extend({
this.$snippetEditorArea.remove();
this.$window.off('.snippets_menu');
this.$document.off('.snippets_menu');
+ this.$scrollingElement.off('.snippets_menu');
}
core.bus.off('deactivate_snippet', this, this._onDeactivateSnippet);
delete this.cacheSnippetTemplate[this.options.snippets];
@@ -1778,13 +1779,6 @@ var SnippetsMenu = Widget.extend({
*/
_getScrollOptions(options = {}) {
return Object.assign({}, options, {
- offsetElements: Object.assign({
- $top: $('#web_editor-top-edit'), // TODO should ideally be retrieved another way
- $left: this.$el,
- }, options.offsetElements),
- scrollBoundaries: Object.assign({
- left: false,
- }, options.scrollBoundaries),
jQueryDraggableOptions: Object.assign({
appendTo: this.$body,
cursor: 'move',
@@ -1954,7 +1948,7 @@ var SnippetsMenu = Widget.extend({
},
},
});
- this.draggableComponent = new SmoothScrollOnDrag(this, $snippets, $('html'), smoothScrollOptions);
+ this.draggableComponent = new SmoothScrollOnDrag(this, $snippets, $().getScrollingElement(), smoothScrollOptions);
},
/**
* Adds the 'o_default_snippet_text' class on nodes which contain only
@@ -2005,29 +1999,14 @@ var SnippetsMenu = Widget.extend({
.prop('disabled', tab !== this.tabs.OPTIONS);
},
/**
- * Scroll to the dropped snippet.
+ * Scrolls to given snippet.
*
* @private
- * @param {jQuery} [$el] - dropped snippet
- * @param {integer} [scrollValue] - scrollValue
+ * @param {jQuery} $el - snippet to scroll to
+ * @return {Promise}
*/
- _scrollToSnippet: function ($el, scrollValue) {
- // return if it's an inner snippet or if it's dropped in a modal
- if ($el.get(0).tagName.toLowerCase() !== 'section' || $el.parent().find("[class^='modal-']").length) {
- return Promise.resolve();
- }
- let headerHeight = 0;
- _.each($('.o_top_fixed_element'), el => headerHeight += $(el).outerHeight());
- return new Promise(resolve => {
- $('html, body').animate(
- {
- scrollTop: scrollValue - headerHeight - 50,
- },
- 700,
- 'swing',
- resolve,
- );
- });
+ async _scrollToSnippet($el) {
+ return dom.scrollTo($el[0], {extraOffset: 50});
},
/**
* @private
diff --git a/addons/website/static/src/js/content/menu.js b/addons/website/static/src/js/content/menu.js
index 27000cfd48d..bcf9ebbaf78 100644
--- a/addons/website/static/src/js/content/menu.js
+++ b/addons/website/static/src/js/content/menu.js
@@ -76,10 +76,8 @@ const BaseAnimatedHeader = animations.Animation.extend({
this._updateMainPaddingTop();
this.el.classList.toggle('o_top_fixed_element', this.fixedHeader && this._isShown());
- const bottom = this.el.getBoundingClientRect().bottom
- || (this.el.nextElementSibling.getBoundingClientRect().top + window.scrollY);
for (const callback of extraMenuUpdateCallbacks) {
- callback(bottom);
+ callback();
}
},
/**
@@ -130,6 +128,14 @@ const BaseAnimatedHeader = animations.Animation.extend({
_toggleFixedHeader: function (useFixed = true) {
this.fixedHeader = useFixed;
this.el.classList.toggle('o_header_affixed', useFixed);
+ // Compensate scrollbar
+ if (useFixed) {
+ const scrollableEl = this.$el.parent().closestScrollable()[0];
+ const style = window.getComputedStyle(this.el);
+ this.el.style.setProperty('right', `${parseInt(style['right']) + scrollableEl.offsetWidth - scrollableEl.clientWidth}px`, 'important');
+ } else {
+ this.el.style.removeProperty('right');
+ }
this._adaptToHeaderChange();
},
/**
diff --git a/addons/website/static/src/js/content/snippets.animation.js b/addons/website/static/src/js/content/snippets.animation.js
index 81640cde197..368be2e810b 100644
--- a/addons/website/static/src/js/content/snippets.animation.js
+++ b/addons/website/static/src/js/content/snippets.animation.js
@@ -8,6 +8,7 @@ odoo.define('website.content.snippets.animation', function (require) {
var Class = require('web.Class');
var config = require('web.config');
var core = require('web.core');
+const dom = require('web.dom');
var mixins = require('web.mixins');
var publicWidget = require('web.public.widget');
var utils = require('web.utils');
@@ -123,12 +124,13 @@ var AnimationEffect = Class.extend(mixins.ParentedMixin, {
// Initialize the animation startEvents, startTarget, endEvents, endTarget and callbacks
this._updateCallback = updateCallback;
this.startEvents = startEvents || 'scroll';
- this.$startTarget = $($startTarget || window);
+ this.$startTarget = $($startTarget ? $startTarget : this.startEvents === 'scroll' ? $().getScrollingElement()[0] : window);
if (options.getStateCallback) {
this._getStateCallback = options.getStateCallback;
- } else if (this.startEvents === 'scroll' && this.$startTarget[0] === window) {
+ } else if (this.startEvents === 'scroll' && this.$startTarget[0] === $().getScrollingElement()[0]) {
+ const $scrollable = this.$startTarget;
this._getStateCallback = function () {
- return window.pageYOffset;
+ return $scrollable.scrollTop();
};
} else if (this.startEvents === 'resize' && this.$startTarget[0] === window) {
this._getStateCallback = function () {
@@ -898,38 +900,19 @@ registry.anchorSlide = publicWidget.Widget.extend({
* @private
* @param {jQuery} $el the element to scroll to.
* @param {string} [scrollValue='true'] scroll value
+ * @returns {Promise}
*/
- _scrollTo: function ($el, scrollValue = 'true', forcedDelay = undefined) {
- const _computeScrollTo = () => {
- const headerHeight = this._computeHeaderHeight();
- const offset = $el.css('position') !== 'fixed' ? $el.offset().top : $el.position().top;
- return offset - headerHeight;
- };
- const originalScrollTo = _computeScrollTo();
- return new Promise(resolve => {
- $('html, body').animate({
- scrollTop: originalScrollTo,
- }, {
- duration: forcedDelay || (scrollValue === 'true' ? 500 : 0),
- progress: (a, b, remainingMs) => {
- const scrollTo = _computeScrollTo();
- if (Math.abs(scrollTo - originalScrollTo) >= 1.0) {
- $('html, body').stop();
- this._scrollTo($el, scrollValue, remainingMs).then(() => resolve());
- }
- },
- complete: () => resolve(),
- });
+ async _scrollTo($el, scrollValue = 'true') {
+ return dom.scrollTo($el[0], {
+ duration: scrollValue === 'true' ? 500 : 0,
+ extraOffset: this._computeExtraOffset(),
});
},
/**
* @private
*/
- _computeHeaderHeight: function () {
- let headerHeight = 0;
- const $navbarFixed = $('.o_top_fixed_element');
- _.each($navbarFixed, el => headerHeight += $(el).outerHeight());
- return headerHeight;
+ _computeExtraOffset() {
+ return 0;
},
//--------------------------------------------------------------------------
diff --git a/addons/website/static/src/js/editor/snippets.editor.js b/addons/website/static/src/js/editor/snippets.editor.js
index e226fd70543..40f999f836c 100644
--- a/addons/website/static/src/js/editor/snippets.editor.js
+++ b/addons/website/static/src/js/editor/snippets.editor.js
@@ -100,6 +100,7 @@ weSnippetEditor.Class.include({
if (!options.offsetElements || !options.offsetElements.$top) {
const $header = $('#top');
if ($header.length) {
+ finalOptions.offsetElements = finalOptions.offsetElements || {};
finalOptions.offsetElements.$top = $header;
}
}
diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js
index 5819402c93a..3b53a594bd7 100644
--- a/addons/website/static/src/js/editor/snippets.options.js
+++ b/addons/website/static/src/js/editor/snippets.options.js
@@ -5,6 +5,7 @@ const {ColorpickerWidget} = require('web.Colorpicker');
const config = require('web.config');
var core = require('web.core');
var Dialog = require('web.Dialog');
+const dom = require('web.dom');
const weUtils = require('web_editor.utils');
var options = require('web_editor.snippets.options');
const wUtils = require('website.utils');
@@ -2504,13 +2505,10 @@ options.registry.SnippetMove = options.Class.extend({
break;
}
if (params.name === 'move_up_opt' || params.name === 'move_down_opt') {
- $('html, body').animate(
- {
- scrollTop: this.$target.offset().top - $(window).height() / 2,
- },
- 500,
- 'linear',
- );
+ dom.scrollTo(this.$target[0], {
+ extraOffset: 50,
+ easing: 'linear',
+ });
}
},
});
diff --git a/addons/website/static/src/snippets/s_table_of_content/000.js b/addons/website/static/src/snippets/s_table_of_content/000.js
index ac610304e48..68250bc88fa 100644
--- a/addons/website/static/src/snippets/s_table_of_content/000.js
+++ b/addons/website/static/src/snippets/s_table_of_content/000.js
@@ -11,18 +11,18 @@ const TableOfContent = publicWidget.Widget.extend({
/**
* @override
*/
- start: function () {
- this._initializeNavbarTopPosition();
+ async start() {
+ await this._super(...arguments);
+ this._updateTableOfContentNavbarPosition();
extraMenuUpdateCallbacks.push(this._updateTableOfContentNavbarPosition.bind(this));
- return this._super.apply(this, arguments);
},
/**
* @override
*/
- destroy: function () {
+ destroy() {
this.$target.css('top', '');
this.$target.find('.s_table_of_content_navbar').css('top', '');
- this._super.apply(this, arguments);
+ this._super(...arguments);
},
//--------------------------------------------------------------------------
@@ -30,26 +30,16 @@ const TableOfContent = publicWidget.Widget.extend({
//--------------------------------------------------------------------------
/**
- * Initialize the top position of the snippet navbar according to the height
- * of the headers of the page.
- *
* @private
*/
- _initializeNavbarTopPosition: function () {
- let headerHeight = 0;
+ _updateTableOfContentNavbarPosition() {
+ let position = 0;
const $fixedElements = $('.o_top_fixed_element');
- _.each($fixedElements, el => headerHeight += $(el).outerHeight());
- this._updateTableOfContentNavbarPosition(headerHeight);
- },
- /**
- * @private
- * @param {number} position
- */
- _updateTableOfContentNavbarPosition: function (position) {
+ _.each($fixedElements, el => position += $(el).outerHeight());
const isHorizontalNavbar = this.$target.hasClass('s_table_of_content_horizontal_navbar');
this.$target.css('top', isHorizontalNavbar ? position : '');
this.$target.find('.s_table_of_content_navbar').css('top', isHorizontalNavbar ? '' : position + 20);
- $('body').scrollspy({target: '.s_table_of_content_navbar', offset: position + 100});
+ $().getScrollingElement().scrollspy({target: '.s_table_of_content_navbar', method: 'offset', offset: position + 100});
},
});
@@ -66,15 +56,15 @@ publicWidget.registry.anchorSlide.include({
* @override
* @private
*/
- _computeHeaderHeight: function () {
- let headerHeight = this._super(...arguments);
+ _computeExtraOffset() {
+ let extraOffset = this._super(...arguments);
if (this.$el.hasClass('table_of_content_link')) {
const tableOfContentNavbarEl = this.$el.closest('.s_table_of_content_navbar_sticky.s_table_of_content_horizontal_navbar');
if (tableOfContentNavbarEl.length > 0) {
- headerHeight += $(tableOfContentNavbarEl).outerHeight();
+ extraOffset += $(tableOfContentNavbarEl).outerHeight();
}
}
- return headerHeight;
+ return extraOffset;
},
});
diff --git a/addons/website/views/website_navbar_templates.xml b/addons/website/views/website_navbar_templates.xml
index 6537ed5e646..1126a8aaf5a 100644
--- a/addons/website/views/website_navbar_templates.xml
+++ b/addons/website/views/website_navbar_templates.xml
@@ -20,7 +20,7 @@
-