Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions addons/website/static/src/interactions/_example.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,11 @@ class TogglableBackgroundSection extends Interaction {
}
}

/*
registry
.category("website.active_elements")
.add("website.toggle_background", TogglableBackgroundSection);
*/

// -----------------------------------------------------------------------------
// Example of interaction
Expand All @@ -53,9 +55,11 @@ class FunNotificationThing extends Interaction {
}
}

/*
registry
.category("website.active_elements")
.add("website.fun_notification", FunNotificationThing);
*/

// -----------------------------------------------------------------------------
// Example of mounted component
Expand All @@ -79,4 +83,6 @@ class Counter extends Component {
}
}

/*
registry.category("website.active_elements").add("website.counter", Counter);
*/
99 changes: 99 additions & 0 deletions addons/website/static/src/interactions/anchor_slide.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { registry } from "@web/core/registry";
import { Interaction } from "@website/core/interaction";
import { scrollTo } from "@web_editor/js/common/scrolling";

export class AnchorSlide extends Interaction {
static selector = "a[href^='/'][href*='#'], a[href^='#']";
static dynamicContent = {
"_root": {
"t-on-click": "animateClick",
},
};

/**
* @param {DOMElement} el the element to scroll to.
* @param {string} [scrollValue='true'] scroll value
* @returns {Promise}
*/
scrollTo(el, scrollValue="true") {
return scrollTo(el, {
duration: scrollValue === "true" ? 500 : 0,
extraOffset: this.computeExtraOffset(),
});
}
/**
* To be overridden.
*/
computeExtraOffset() {
return 0;
}
/**
*/
animateClick(ev) {
const ensureSlash = path => path.endsWith("/") ? path : path + "/";
if (ensureSlash(this.el.pathname) !== ensureSlash(window.location.pathname)) {
return;
}
// Avoid flicker at destination in case of ending "/" difference.
if (this.el.pathname !== window.location.pathname) {
this.el.pathname = window.location.pathname;
}
let hash = this.el.hash;
if (!hash.length) {
return;
}
// Escape special characters to make the selector work.
// TODO Not rely on jQuery to escape anchor name for selector.
hash = "#" + $.escapeSelector(hash.substring(1));
const anchorEl = this.el.ownerDocument.querySelector(hash);
const scrollValue = anchorEl?.dataset.anchor;
if (!anchorEl || !scrollValue) {
return;
}

const offcanvasEl = this.el.closest(".offcanvas.o_navbar_mobile");
if (offcanvasEl && offcanvasEl.classList.contains("show")) {
// Special case for anchors in offcanvas in mobile: we can't just
// scrollTo() after preventDefault because preventDefault would
// prevent the offcanvas to be closed. The choice is then to close
// it ourselves manually and once it's fully closed, then start our
// own smooth scrolling.
ev.preventDefault();
Offcanvas.getInstance(offcanvasEl).hide();
offcanvasEl.addEventListener("hidden.bs.offcanvas",
() => {
this.manageScroll(hash, anchorEl, scrollValue);
},
// the listener must be automatically removed when invoked
{ once: true }
);
} else {
ev.preventDefault();
this.manageScroll(hash, anchorEl, scrollValue);
}
}
/**
*
* @param {string} hash
* @param {DOMElement} anchorEl the element to scroll to.
* @param {string} [scrollValue='true'] scroll value
*/
manageScroll(hash, anchorEl, scrollValue = "true") {
if (hash === "#top" || hash === "#bottom") {
// If the anchor targets #top or #bottom, directly call the
// "scrollTo" function. The reason is that the header or the footer
// could have been removed from the DOM. By receiving a string as
// parameter, the "scrollTo" function handles the scroll to the top
// or to the bottom of the document even if the header or the
// footer is removed from the DOM.
scrollTo(hash, {
duration: 500,
extraOffset: this.computeExtraOffset(),
});
} else {
this.scrollTo(anchorEl, scrollValue);
}
}
}

registry.category("website.active_elements").add("website.anchor_slide", AnchorSlide);
23 changes: 23 additions & 0 deletions addons/website/static/src/interactions/scroll_button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { registry } from "@web/core/registry";
import { isVisible } from "@web/core/utils/ui";
import { AnchorSlide } from "@website/interactions/anchor_slide";

export class ScrollButton extends AnchorSlide {
static selector = ".o_scroll_button";

animateClick(ev) {
ev.preventDefault();
// Scroll to the next visible element after the current one.
const currentSectionEl = this.el.closest("section");
let nextEl = currentSectionEl.nextElementSibling;
while (nextEl) {
if (isVisible(nextEl)) {
this.scrollTo(nextEl);
return;
}
nextEl = nextEl.nextElementSibling;
}
}
}

registry.category("website.active_elements").add("website.scroll_button", ScrollButton);
124 changes: 0 additions & 124 deletions addons/website/static/src/js/content/snippets.animation.js
Original file line number Diff line number Diff line change
Expand Up @@ -1126,109 +1126,6 @@ registry.backgroundVideo = publicWidget.Widget.extend(MobileYoutubeAutoplayMixin
},
});

registry.anchorSlide = publicWidget.Widget.extend({
selector: 'a[href^="/"][href*="#"], a[href^="#"]',
events: {
'click': '_onAnimateClick',
},

//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------

/**
* @private
* @param {jQuery} $el the element to scroll to.
* @param {string} [scrollValue='true'] scroll value
* @returns {Promise}
*/
async _scrollTo($el, scrollValue = 'true') {
return scrollTo($el[0], {
duration: scrollValue === "true" ? 500 : 0,
extraOffset: this._computeExtraOffset(),
});
},
/**
* @private
*/
_computeExtraOffset() {
return 0;
},

//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------

/**
* @private
*/
_onAnimateClick: function (ev) {
const ensureSlash = path => path.endsWith("/") ? path : path + "/";
if (ensureSlash(this.el.pathname) !== ensureSlash(window.location.pathname)) {
return;
}
// Avoid flicker at destination in case of ending "/" difference.
if (this.el.pathname !== window.location.pathname) {
this.el.pathname = window.location.pathname;
}
var hash = this.el.hash;
if (!hash.length) {
return;
}
// Escape special characters to make the jQuery selector to work.
hash = '#' + $.escapeSelector(hash.substring(1));
var $anchor = $(hash);
const scrollValue = $anchor.attr('data-anchor');
if (!$anchor.length || !scrollValue) {
return;
}

const offcanvasEl = this.el.closest('.offcanvas.o_navbar_mobile');
if (offcanvasEl && offcanvasEl.classList.contains('show')) {
// Special case for anchors in offcanvas in mobile: we can't just
// _scrollTo() after preventDefault because preventDefault would
// prevent the offcanvas to be closed. The choice is then to close
// it ourselves manually and once it's fully closed, then start our
// own smooth scrolling.
ev.preventDefault();
Offcanvas.getInstance(offcanvasEl).hide();
offcanvasEl.addEventListener('hidden.bs.offcanvas',
() => {
this._manageScroll(hash, $anchor, scrollValue);
},
// the listener must be automatically removed when invoked
{ once: true }
);
} else {
ev.preventDefault();
this._manageScroll(hash, $anchor, scrollValue);
}
},
/**
*
* @param {string} hash
* @param {jQuery} $el the element to scroll to.
* @param {string} [scrollValue='true'] scroll value
* @private
*/
_manageScroll(hash, $anchor, scrollValue = "true") {
if (hash === "#top" || hash === "#bottom") {
// If the anchor targets #top or #bottom, directly call the
// "scrollTo" function. The reason is that the header or the footer
// could have been removed from the DOM. By receiving a string as
// parameter, the "scrollTo" function handles the scroll to the top
// or to the bottom of the document even if the header or the
// footer is removed from the DOM.
scrollTo(hash, {
duration: 500,
extraOffset: this._computeExtraOffset(),
});
} else {
this._scrollTo($anchor, scrollValue);
}
},
});

registry.FullScreenHeight = publicWidget.Widget.extend({
selector: '.o_full_screen_height',
disabledInEditableMode: false,
Expand Down Expand Up @@ -1288,27 +1185,6 @@ registry.FullScreenHeight = publicWidget.Widget.extend({
},
});

registry.ScrollButton = registry.anchorSlide.extend({
selector: '.o_scroll_button',

/**
* @override
*/
_onAnimateClick: function (ev) {
ev.preventDefault();
// Scroll to the next visible element after the current one.
const currentSectionEl = this.el.closest('section');
let nextEl = currentSectionEl.nextElementSibling;
while (nextEl) {
if ($(nextEl).is(':visible')) {
this._scrollTo($(nextEl));
return;
}
nextEl = nextEl.nextElementSibling;
}
},
});

registry.FooterSlideout = publicWidget.Widget.extend({
selector: '#wrapwrap',
selectorHas: '.o_footer_slideout',
Expand Down
23 changes: 10 additions & 13 deletions addons/website/static/src/snippets/s_table_of_content/000.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { registry } from "@web/core/registry";
import { patch } from "@web/core/utils/patch";
import publicWidget from "@web/legacy/js/public/public_widget";
import {extraMenuUpdateCallbacks} from "@website/js/content/menu";
import { closestScrollable } from "@web_editor/js/common/scrolling";
import { AnchorSlide } from "@website/interactions/anchor_slide";

const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item';
const CLASS_NAME_ACTIVE = 'active';
Expand Down Expand Up @@ -222,25 +225,19 @@ const TableOfContent = publicWidget.Widget.extend({
},
});

publicWidget.registry.anchorSlide.include({

//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------

patch(AnchorSlide.prototype, {
/**
* Overridden to add the height of the horizontal sticky navbar at the scroll value
* when the link is from the table of content navbar
*
* @override
* @private
*/
_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) {
extraOffset += $(tableOfContentNavbarEl).outerHeight();
computeExtraOffset() {
let extraOffset = super.computeExtraOffset(...arguments);
if (this.el.classList.contains("table_of_content_link")) {
const tableOfContentNavbarEl = this.el.closest(".s_table_of_content_navbar_sticky.s_table_of_content_horizontal_navbar");
if (tableOfContentNavbarEl) {
extraOffset += tableOfContentNavbarEl.getBoundingClientRect().height;
}
}
return extraOffset;
Expand Down
10 changes: 10 additions & 0 deletions addons/website/static/tests/core/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,13 @@ export function mockSendRequests() {
});
return requests;
}

export function isElementInViewport(el) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
Loading