first commit

This commit is contained in:
2024-07-15 11:28:08 +02:00
commit f52d538ea5
21891 changed files with 6161164 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
module.exports = elementorModules.ViewModule.extend( {
getDefaultSettings: function() {
return {
scrollDuration: 500,
selectors: {
links: 'a[href*="#"]',
targets: '.elementor-element, .elementor-menu-anchor',
scrollable: 'html, body',
},
};
},
getDefaultElements: function() {
var $ = jQuery,
selectors = this.getSettings( 'selectors' );
return {
$scrollable: $( selectors.scrollable ),
};
},
bindEvents: function() {
elementorFrontend.elements.$document.on( 'click', this.getSettings( 'selectors.links' ), this.handleAnchorLinks );
},
handleAnchorLinks: function( event ) {
var clickedLink = event.currentTarget,
isSamePathname = ( location.pathname === clickedLink.pathname ),
isSameHostname = ( location.hostname === clickedLink.hostname ),
$anchor;
if ( ! isSameHostname || ! isSamePathname || clickedLink.hash.length < 2 ) {
return;
}
try {
$anchor = jQuery( clickedLink.hash ).filter( this.getSettings( 'selectors.targets' ) );
} catch ( e ) {
return;
}
if ( ! $anchor.length ) {
return;
}
var scrollTop = $anchor.offset().top,
$wpAdminBar = elementorFrontend.elements.$wpAdminBar,
$activeStickies = jQuery( '.elementor-section.elementor-sticky--active:visible' ),
maxStickyHeight = 0;
if ( $wpAdminBar.length > 0 ) {
scrollTop -= $wpAdminBar.height();
}
// Offset height of tallest sticky
if ( $activeStickies.length > 0 ) {
maxStickyHeight = Math.max.apply( null, $activeStickies.map( function() {
return jQuery( this ).outerHeight();
} ).get() );
scrollTop -= maxStickyHeight;
}
event.preventDefault();
scrollTop = elementorFrontend.hooks.applyFilters( 'frontend/handlers/menu_anchor/scroll_top_distance', scrollTop );
this.elements.$scrollable.animate( {
scrollTop: scrollTop,
}, this.getSettings( 'scrollDuration' ), 'linear' );
},
onInit: function() {
elementorModules.ViewModule.prototype.onInit.apply( this, arguments );
this.bindEvents();
},
} );

View File

@@ -0,0 +1,53 @@
export default class AssetsLoader {
getScriptElement( src ) {
const scriptElement = document.createElement( 'script' );
scriptElement.src = src;
return scriptElement;
}
getStyleElement( src ) {
const styleElement = document.createElement( 'link' );
styleElement.rel = 'stylesheet';
styleElement.href = src;
return styleElement;
}
load( type, key ) {
const assetData = AssetsLoader.assets[ type ][ key ];
if ( ! assetData.loader ) {
assetData.loader = new Promise( ( resolve ) => {
const element = 'style' === type ? this.getStyleElement( assetData.src ) : this.getScriptElement( assetData.src );
element.onload = () => resolve( true );
const parent = 'head' === assetData.parent ? assetData.parent : 'body';
document[ parent ].appendChild( element );
} );
}
return assetData.loader;
}
}
const fileSuffix = elementorFrontendConfig.environmentMode.isScriptDebug ? '' : '.min';
AssetsLoader.assets = {
script: {
dialog: {
src: `${ elementorFrontendConfig.urls.assets }lib/dialog/dialog${ fileSuffix }.js?ver=4.8.1`,
},
'share-link': {
src: `${ elementorFrontendConfig.urls.assets }lib/share-link/share-link${ fileSuffix }.js?ver=${ elementorFrontendConfig.version }`,
},
swiper: {
src: `${ elementorFrontendConfig.urls.assets }lib/swiper/swiper${ fileSuffix }.js?ver=5.3.6`,
},
},
style: {},
};

View File

@@ -0,0 +1,95 @@
export default class LightboxManager extends elementorModules.ViewModule {
static getLightbox() {
const lightboxPromise = new Promise( ( resolveLightbox ) => {
import(
/* webpackChunkName: 'lightbox' */
`elementor-frontend/utils/lightbox/lightbox`
).then( ( { default: LightboxModule } ) => resolveLightbox( new LightboxModule() ) );
} ),
dialogPromise = elementorFrontend.utils.assetsLoader.load( 'script', 'dialog' ),
shareLinkPromise = elementorFrontend.utils.assetsLoader.load( 'script', 'share-link' );
return Promise.all( [ lightboxPromise, dialogPromise, shareLinkPromise ] ).then( () => lightboxPromise );
}
getDefaultSettings() {
return {
selectors: {
links: 'a, [data-elementor-lightbox]',
},
};
}
getDefaultElements() {
return {
$links: jQuery( this.getSettings( 'selectors.links' ) ),
};
}
isLightboxLink( element ) {
// Check for lowercase `a` to make sure it works also for links inside SVGs.
if ( 'a' === element.tagName.toLowerCase() && ( element.hasAttribute( 'download' ) || ! /^[^?]+\.(png|jpe?g|gif|svg|webp)(\?.*)?$/i.test( element.href ) ) ) {
return false;
}
const generalOpenInLightbox = elementorFrontend.getKitSettings( 'global_image_lightbox' ),
currentLinkOpenInLightbox = element.dataset.elementorOpenLightbox;
return 'yes' === currentLinkOpenInLightbox || ( generalOpenInLightbox && 'no' !== currentLinkOpenInLightbox );
}
async onLinkClick( event ) {
const element = event.currentTarget,
$target = jQuery( event.target ),
editMode = elementorFrontend.isEditMode(),
isClickInsideElementor = ! ! $target.closest( '.elementor-edit-area' ).length;
if ( ! this.isLightboxLink( element ) ) {
if ( editMode && isClickInsideElementor ) {
event.preventDefault();
}
return;
}
event.preventDefault();
if ( editMode && ! elementor.getPreferences( 'lightbox_in_editor' ) ) {
return;
}
const lightbox = this.isOptimizedAssetsLoading() ? await LightboxManager.getLightbox() : elementorFrontend.utils.lightbox;
lightbox.createLightbox( element );
}
isOptimizedAssetsLoading() {
return elementorFrontend.config.experimentalFeatures.e_optimized_assets_loading;
}
bindEvents() {
elementorFrontend.elements.$document.on(
'click',
this.getSettings( 'selectors.links' ),
( event ) => this.onLinkClick( event )
);
}
onInit( ...args ) {
super.onInit( ...args );
if ( ! this.isOptimizedAssetsLoading() || elementorFrontend.isEditMode() ) {
return;
}
// Detecting lightbox links on init will reduce the time of waiting to the lightbox to be display on slow connections.
this.elements.$links.each( ( index, element ) => {
if ( this.isLightboxLink( element ) ) {
LightboxManager.getLightbox();
// Breaking the iteration when the library loading has already been triggered.
return false;
}
} );
}
}

View File

@@ -0,0 +1,935 @@
import screenfull from './screenfull';
module.exports = elementorModules.ViewModule.extend( {
oldAspectRatio: null,
oldAnimation: null,
swiper: null,
player: null,
getDefaultSettings: function() {
return {
classes: {
aspectRatio: 'elementor-aspect-ratio-%s',
item: 'elementor-lightbox-item',
image: 'elementor-lightbox-image',
videoContainer: 'elementor-video-container',
videoWrapper: 'elementor-fit-aspect-ratio',
playButton: 'elementor-custom-embed-play',
playButtonIcon: 'fa',
playing: 'elementor-playing',
hidden: 'elementor-hidden',
invisible: 'elementor-invisible',
preventClose: 'elementor-lightbox-prevent-close',
slideshow: {
container: 'swiper-container',
slidesWrapper: 'swiper-wrapper',
prevButton: 'elementor-swiper-button elementor-swiper-button-prev',
nextButton: 'elementor-swiper-button elementor-swiper-button-next',
prevButtonIcon: 'eicon-chevron-left',
nextButtonIcon: 'eicon-chevron-right',
slide: 'swiper-slide',
header: 'elementor-slideshow__header',
footer: 'elementor-slideshow__footer',
title: 'elementor-slideshow__title',
description: 'elementor-slideshow__description',
counter: 'elementor-slideshow__counter',
iconExpand: 'eicon-frame-expand',
iconShrink: 'eicon-frame-minimize',
iconZoomIn: 'eicon-zoom-in-bold',
iconZoomOut: 'eicon-zoom-out-bold',
iconShare: 'eicon-share-arrow',
shareMenu: 'elementor-slideshow__share-menu',
shareLinks: 'elementor-slideshow__share-links',
hideUiVisibility: 'elementor-slideshow--ui-hidden',
shareMode: 'elementor-slideshow--share-mode',
fullscreenMode: 'elementor-slideshow--fullscreen-mode',
zoomMode: 'elementor-slideshow--zoom-mode',
},
},
selectors: {
image: '.elementor-lightbox-image',
links: 'a, [data-elementor-lightbox]',
slideshow: {
activeSlide: '.swiper-slide-active',
prevSlide: '.swiper-slide-prev',
nextSlide: '.swiper-slide-next',
},
},
modalOptions: {
id: 'elementor-lightbox',
entranceAnimation: 'zoomIn',
videoAspectRatio: 169,
position: {
enable: false,
},
},
};
},
getModal: function() {
if ( ! module.exports.modal ) {
this.initModal();
}
return module.exports.modal;
},
initModal: function() {
const modal = module.exports.modal = elementorFrontend.getDialogsManager().createWidget( 'lightbox', {
className: 'elementor-lightbox',
closeButton: true,
closeButtonOptions: {
iconClass: 'eicon-close',
attributes: {
tabindex: 0,
role: 'button',
'aria-label': elementorFrontend.config.i18n.close + ' (Esc)',
},
},
selectors: {
preventClose: '.' + this.getSettings( 'classes.preventClose' ),
},
hide: {
onClick: true,
},
} );
modal.on( 'hide', function() {
modal.setMessage( '' );
} );
},
showModal: function( options ) {
if ( options.url && ! options.url.startsWith( 'http' ) ) {
return;
}
this.elements.$closeButton = this.getModal().getElements( 'closeButton' );
this.$buttons = this.elements.$closeButton;
this.focusedButton = null;
const self = this,
defaultOptions = self.getDefaultSettings().modalOptions;
self.id = options.id;
self.setSettings( 'modalOptions', jQuery.extend( defaultOptions, options.modalOptions ) );
const modal = self.getModal();
modal.setID( self.getSettings( 'modalOptions.id' ) );
modal.onShow = function() {
DialogsManager.getWidgetType( 'lightbox' ).prototype.onShow.apply( modal, arguments );
self.setEntranceAnimation();
};
modal.onHide = function() {
DialogsManager.getWidgetType( 'lightbox' ).prototype.onHide.apply( modal, arguments );
modal.getElements( 'message' ).removeClass( 'animated' );
if ( screenfull.isFullscreen ) {
self.deactivateFullscreen();
}
self.unbindHotKeys();
};
switch ( options.type ) {
case 'video':
self.setVideoContent( options );
break;
case 'image':
const slides = [ {
image: options.url,
index: 0,
title: options.title,
description: options.description,
} ];
options.slideshow = {
slides,
swiper: {
loop: false,
pagination: false,
},
};
case 'slideshow':
self.setSlideshowContent( options.slideshow );
break;
default:
self.setHTMLContent( options.html );
}
modal.show();
},
createLightbox: function( element ) {
let lightboxData = {};
if ( element.dataset.elementorLightbox ) {
lightboxData = JSON.parse( element.dataset.elementorLightbox );
}
if ( lightboxData.type && 'slideshow' !== lightboxData.type ) {
this.showModal( lightboxData );
return;
}
if ( ! element.dataset.elementorLightboxSlideshow ) {
const slideshowID = 'single-img';
this.showModal( {
type: 'image',
id: slideshowID,
url: element.href,
title: element.dataset.elementorLightboxTitle,
description: element.dataset.elementorLightboxDescription,
modalOptions: {
id: 'elementor-lightbox-slideshow-' + slideshowID,
},
} );
return;
}
const initialSlideURL = element.dataset.elementorLightboxVideo || element.href;
this.openSlideshow( element.dataset.elementorLightboxSlideshow, initialSlideURL );
},
setHTMLContent: function( html ) {
if ( window.elementorCommon ) {
elementorCommon.helpers.hardDeprecated( 'elementorFrontend.utils.lightbox.setHTMLContent', '3.1.4' );
}
this.getModal().setMessage( html );
},
setVideoContent: function( options ) {
const $ = jQuery,
classes = this.getSettings( 'classes' ),
$videoContainer = $( '<div>', { class: `${ classes.videoContainer } ${ classes.preventClose }` } ),
$videoWrapper = $( '<div>', { class: classes.videoWrapper } ),
modal = this.getModal();
let $videoElement;
if ( 'hosted' === options.videoType ) {
const videoParams = $.extend( { src: options.url, autoplay: '' }, options.videoParams );
$videoElement = $( '<video>', videoParams );
} else {
const videoURL = options.url.replace( '&autoplay=0', '' ) + '&autoplay=1';
$videoElement = $( '<iframe>', { src: videoURL, allowfullscreen: 1 } );
}
$videoContainer.append( $videoWrapper );
$videoWrapper.append( $videoElement );
modal.setMessage( $videoContainer );
this.setVideoAspectRatio();
const onHideMethod = modal.onHide;
modal.onHide = function() {
onHideMethod();
this.$buttons = jQuery();
this.focusedButton = null;
modal.getElements( 'message' ).removeClass( 'elementor-fit-aspect-ratio' );
};
},
getShareLinks: function() {
const { i18n } = elementorFrontend.config,
socialNetworks = {
facebook: i18n.shareOnFacebook,
twitter: i18n.shareOnTwitter,
pinterest: i18n.pinIt,
},
$ = jQuery,
classes = this.getSettings( 'classes' ),
selectors = this.getSettings( 'selectors' ),
$linkList = $( '<div>', { class: classes.slideshow.shareLinks } ),
$activeSlide = this.getSlide( 'active' ),
$image = $activeSlide.find( selectors.image ),
videoUrl = $activeSlide.data( 'elementor-slideshow-video' );
let itemUrl;
if ( videoUrl ) {
itemUrl = videoUrl;
} else {
itemUrl = $image.attr( 'src' );
}
$.each( socialNetworks, ( key, networkLabel ) => {
const $link = $( '<a>', { href: this.createShareLink( key, itemUrl ), target: '_blank' } ).text( networkLabel );
$link.prepend( $( '<i>', { class: 'eicon-' + key } ) );
$linkList.append( $link );
} );
if ( ! videoUrl ) {
$linkList.append( $( '<a>', { href: itemUrl, download: '' } )
.text( i18n.downloadImage )
.prepend( $( '<i>', { class: 'eicon-download-bold', 'aria-label': i18n.download } ) ) );
}
return $linkList;
},
createShareLink: function( networkName, itemUrl ) {
const options = {};
if ( 'pinterest' === networkName ) {
options.image = encodeURIComponent( itemUrl );
} else {
const hash = elementorFrontend.utils.urlActions.createActionHash( 'lightbox', {
id: this.id,
url: itemUrl,
} );
options.url = encodeURIComponent( location.href.replace( /#.*/, '' ) ) + hash;
}
return ShareLink.getNetworkLink( networkName, options );
},
getSlideshowHeader: function() {
const { i18n } = elementorFrontend.config,
$ = jQuery,
showCounter = 'yes' === elementorFrontend.getKitSettings( 'lightbox_enable_counter' ),
showFullscreen = 'yes' === elementorFrontend.getKitSettings( 'lightbox_enable_fullscreen' ),
showZoom = 'yes' === elementorFrontend.getKitSettings( 'lightbox_enable_zoom' ),
showShare = 'yes' === elementorFrontend.getKitSettings( 'lightbox_enable_share' ),
classes = this.getSettings( 'classes' ),
slideshowClasses = classes.slideshow,
elements = this.elements;
if ( ! ( showCounter || showFullscreen || showZoom || showShare ) ) {
return;
}
elements.$header = $( '<header>', { class: slideshowClasses.header + ' ' + classes.preventClose } );
if ( showShare ) {
elements.$iconShare = $( '<i>', {
class: slideshowClasses.iconShare,
role: 'button',
'aria-label': i18n.share,
'aria-expanded': false,
} ).append( $( '<span>' ) );
const $shareLinks = $( '<div>' );
$shareLinks.on( 'click', ( e ) => {
e.stopPropagation();
} );
elements.$shareMenu = $( '<div>', { class: slideshowClasses.shareMenu } ).append( $shareLinks );
elements.$iconShare.add( elements.$shareMenu ).on( 'click', this.toggleShareMenu );
elements.$header.append( elements.$iconShare, elements.$shareMenu );
this.$buttons = this.$buttons.add( elements.$iconShare );
}
if ( showZoom ) {
elements.$iconZoom = $( '<i>', {
class: slideshowClasses.iconZoomIn,
role: 'switch',
'aria-checked': false,
'aria-label': i18n.zoom,
} );
elements.$iconZoom.on( 'click', this.toggleZoomMode );
elements.$header.append( elements.$iconZoom );
this.$buttons = this.$buttons.add( elements.$iconZoom );
}
if ( showFullscreen ) {
elements.$iconExpand = $( '<i>', {
class: slideshowClasses.iconExpand,
role: 'switch',
'aria-checked': false,
'aria-label': i18n.fullscreen,
} ).append( $( '<span>' ), $( '<span>' ) );
elements.$iconExpand.on( 'click', this.toggleFullscreen );
elements.$header.append( elements.$iconExpand );
this.$buttons = this.$buttons.add( elements.$iconExpand );
}
if ( showCounter ) {
elements.$counter = $( '<span>', { class: slideshowClasses.counter } );
elements.$header.append( elements.$counter );
}
return elements.$header;
},
toggleFullscreen: function() {
if ( screenfull.isFullscreen ) {
this.deactivateFullscreen();
} else if ( screenfull.isEnabled ) {
this.activateFullscreen();
}
},
toggleZoomMode: function() {
if ( 1 !== this.swiper.zoom.scale ) {
this.deactivateZoom();
} else {
this.activateZoom();
}
},
toggleShareMenu: function() {
if ( this.shareMode ) {
this.deactivateShareMode();
} else {
this.elements.$shareMenu.html( this.getShareLinks() );
this.activateShareMode();
}
},
activateShareMode: function() {
const classes = this.getSettings( 'classes' );
this.elements.$container.addClass( classes.slideshow.shareMode );
this.elements.$iconShare.attr( 'aria-expanded', true );
// Prevent swiper interactions while in share mode
this.swiper.detachEvents();
// Temporarily replace tabbable buttons with share-menu items
this.$originalButtons = this.$buttons;
this.$buttons = this.elements.$iconShare.add( this.elements.$shareMenu.find( 'a' ) );
this.shareMode = true;
},
deactivateShareMode: function() {
const classes = this.getSettings( 'classes' );
this.elements.$container.removeClass( classes.slideshow.shareMode );
this.elements.$iconShare.attr( 'aria-expanded', false );
this.swiper.attachEvents();
this.$buttons = this.$originalButtons;
this.shareMode = false;
},
activateFullscreen: function() {
const classes = this.getSettings( 'classes' );
screenfull.request( this.elements.$container.parents( '.dialog-widget' )[ 0 ] );
this.elements.$iconExpand.removeClass( classes.slideshow.iconExpand )
.addClass( classes.slideshow.iconShrink )
.attr( 'aria-checked', 'true' );
this.elements.$container.addClass( classes.slideshow.fullscreenMode );
},
deactivateFullscreen: function() {
const classes = this.getSettings( 'classes' );
screenfull.exit();
this.elements.$iconExpand.removeClass( classes.slideshow.iconShrink )
.addClass( classes.slideshow.iconExpand )
.attr( 'aria-checked', 'false' );
this.elements.$container.removeClass( classes.slideshow.fullscreenMode );
},
activateZoom: function() {
const swiper = this.swiper,
elements = this.elements,
classes = this.getSettings( 'classes' );
swiper.zoom.in();
swiper.allowSlideNext = false;
swiper.allowSlidePrev = false;
swiper.allowTouchMove = false;
elements.$container.addClass( classes.slideshow.zoomMode );
elements.$iconZoom.removeClass( classes.slideshow.iconZoomIn ).addClass( classes.slideshow.iconZoomOut );
},
deactivateZoom: function() {
const swiper = this.swiper,
elements = this.elements,
classes = this.getSettings( 'classes' );
swiper.zoom.out();
swiper.allowSlideNext = true;
swiper.allowSlidePrev = true;
swiper.allowTouchMove = true;
elements.$container.removeClass( classes.slideshow.zoomMode );
elements.$iconZoom.removeClass( classes.slideshow.iconZoomOut ).addClass( classes.slideshow.iconZoomIn );
},
getSlideshowFooter: function() {
const $ = jQuery,
classes = this.getSettings( 'classes' ),
$footer = $( '<footer>', { class: classes.slideshow.footer + ' ' + classes.preventClose } ),
$title = $( '<div>', { class: classes.slideshow.title } ),
$description = $( '<div>', { class: classes.slideshow.description } );
$footer.append( $title, $description );
return $footer;
},
setSlideshowContent: function( options ) {
const { i18n } = elementorFrontend.config,
$ = jQuery,
isSingleSlide = 1 === options.slides.length,
hasTitle = '' !== elementorFrontend.getKitSettings( 'lightbox_title_src' ),
hasDescription = '' !== elementorFrontend.getKitSettings( 'lightbox_description_src' ),
showFooter = hasTitle || hasDescription,
classes = this.getSettings( 'classes' ),
slideshowClasses = classes.slideshow,
$container = $( '<div>', { class: slideshowClasses.container } ),
$slidesWrapper = $( '<div>', { class: slideshowClasses.slidesWrapper } );
let $prevButton, $nextButton;
options.slides.forEach( ( slide ) => {
let slideClass = slideshowClasses.slide + ' ' + classes.item;
if ( slide.video ) {
slideClass += ' ' + classes.video;
}
const $slide = $( '<div>', { class: slideClass } );
if ( slide.video ) {
$slide.attr( 'data-elementor-slideshow-video', slide.video );
const $playIcon = $( '<div>', { class: classes.playButton } ).html( $( '<i>', { class: classes.playButtonIcon, 'aria-label': i18n.playVideo } ) );
$slide.append( $playIcon );
} else {
const $zoomContainer = $( '<div>', { class: 'swiper-zoom-container' } ),
$slidePlaceholder = $( '<div class="swiper-lazy-preloader"></div>' ),
imageAttributes = {
'data-src': slide.image,
class: classes.image + ' ' + classes.preventClose + ' swiper-lazy',
};
if ( slide.title ) {
imageAttributes[ 'data-title' ] = slide.title;
imageAttributes.alt = slide.title;
}
if ( slide.description ) {
imageAttributes[ 'data-description' ] = slide.description;
imageAttributes.alt += ' - ' + slide.description;
}
const $slideImage = $( '<img>', imageAttributes );
$zoomContainer.append( [ $slideImage, $slidePlaceholder ] );
$slide.append( $zoomContainer );
}
$slidesWrapper.append( $slide );
} );
this.elements.$container = $container;
this.elements.$header = this.getSlideshowHeader();
$container
.prepend( this.elements.$header )
.append( $slidesWrapper );
if ( ! isSingleSlide ) {
$prevButton = $( '<div>', { class: slideshowClasses.prevButton + ' ' + classes.preventClose, 'aria-label': i18n.previous } ).html( $( '<i>', { class: slideshowClasses.prevButtonIcon } ) );
$nextButton = $( '<div>', { class: slideshowClasses.nextButton + ' ' + classes.preventClose, 'aria-label': i18n.next } ).html( $( '<i>', { class: slideshowClasses.nextButtonIcon } ) );
$container.append(
$nextButton,
$prevButton,
);
this.$buttons = this.$buttons.add( $nextButton ).add( $prevButton );
}
if ( showFooter ) {
this.elements.$footer = this.getSlideshowFooter();
$container.append( this.elements.$footer );
}
this.setSettings( 'hideUiTimeout', '' );
$container.on( 'click mousemove keypress', this.showLightboxUi );
const modal = this.getModal();
modal.setMessage( $container );
const onShowMethod = modal.onShow;
modal.onShow = async () => {
onShowMethod();
const swiperOptions = {
pagination: {
el: '.' + slideshowClasses.counter,
type: 'fraction',
},
on: {
slideChangeTransitionEnd: this.onSlideChange,
},
lazy: {
loadPrevNext: true,
},
zoom: true,
spaceBetween: 100,
grabCursor: true,
runCallbacksOnInit: false,
loop: true,
keyboard: true,
handleElementorBreakpoints: true,
};
if ( ! isSingleSlide ) {
swiperOptions.navigation = {
prevEl: $prevButton,
nextEl: $nextButton,
};
}
if ( options.swiper ) {
$.extend( swiperOptions, options.swiper );
}
const Swiper = elementorFrontend.utils.swiper;
this.swiper = await new Swiper( $container, swiperOptions );
// Expose the swiper instance in the frontend
$container.data( 'swiper', this.swiper );
this.setVideoAspectRatio();
this.playSlideVideo();
if ( showFooter ) {
this.updateFooterText();
}
this.bindHotKeys();
this.makeButtonsAccessible();
};
},
makeButtonsAccessible: function() {
this.$buttons
.attr( 'tabindex', 0 )
.on( 'keypress', ( event ) => {
const ENTER_KEY = 13,
SPACE_KEY = 32;
if ( ENTER_KEY === event.which || SPACE_KEY === event.which ) {
jQuery( event.currentTarget ).trigger( 'click' );
}
} );
},
showLightboxUi: function() {
const slideshowClasses = this.getSettings( 'classes' ).slideshow;
this.elements.$container.removeClass( slideshowClasses.hideUiVisibility );
clearTimeout( this.getSettings( 'hideUiTimeout' ) );
this.setSettings( 'hideUiTimeout', setTimeout( () => {
if ( ! this.shareMode ) {
this.elements.$container.addClass( slideshowClasses.hideUiVisibility );
}
}, 3500 ) );
},
bindHotKeys: function() {
this.getModal().getElements( 'window' ).on( 'keydown', this.activeKeyDown );
},
unbindHotKeys: function() {
this.getModal().getElements( 'window' ).off( 'keydown', this.activeKeyDown );
},
activeKeyDown: function( event ) {
this.showLightboxUi();
const TAB_KEY = 9;
if ( event.which === TAB_KEY ) {
const $buttons = this.$buttons;
let focusedButton,
isFirst = false,
isLast = false;
$buttons.each( ( index ) => {
const item = $buttons[ index ];
if ( jQuery( item ).is( ':focus' ) ) {
focusedButton = item;
isFirst = 0 === index;
isLast = $buttons.length - 1 === index;
return false;
}
} );
if ( event.shiftKey ) {
if ( isFirst ) {
event.preventDefault();
$buttons.last().trigger( 'focus' );
}
} else if ( isLast || ! focusedButton ) {
event.preventDefault();
$buttons.first().trigger( 'focus' );
}
}
},
setVideoAspectRatio: function( aspectRatio ) {
aspectRatio = aspectRatio || this.getSettings( 'modalOptions.videoAspectRatio' );
const $widgetContent = this.getModal().getElements( 'widgetContent' ),
oldAspectRatio = this.oldAspectRatio,
aspectRatioClass = this.getSettings( 'classes.aspectRatio' );
this.oldAspectRatio = aspectRatio;
if ( oldAspectRatio ) {
$widgetContent.removeClass( aspectRatioClass.replace( '%s', oldAspectRatio ) );
}
if ( aspectRatio ) {
$widgetContent.addClass( aspectRatioClass.replace( '%s', aspectRatio ) );
}
},
getSlide: function( slideState ) {
return jQuery( this.swiper.slides ).filter( this.getSettings( 'selectors.slideshow.' + slideState + 'Slide' ) );
},
updateFooterText: function() {
if ( ! this.elements.$footer ) {
return;
}
const classes = this.getSettings( 'classes' ),
$activeSlide = this.getSlide( 'active' ),
$image = $activeSlide.find( '.elementor-lightbox-image' ),
titleText = $image.data( 'title' ),
descriptionText = $image.data( 'description' ),
$title = this.elements.$footer.find( '.' + classes.slideshow.title ),
$description = this.elements.$footer.find( '.' + classes.slideshow.description );
$title.text( titleText || '' );
$description.text( descriptionText || '' );
},
playSlideVideo: function() {
const $activeSlide = this.getSlide( 'active' ),
videoURL = $activeSlide.data( 'elementor-slideshow-video' );
if ( ! videoURL ) {
return;
}
const classes = this.getSettings( 'classes' ),
$videoContainer = jQuery( '<div>', { class: classes.videoContainer + ' ' + classes.invisible } ),
$videoWrapper = jQuery( '<div>', { class: classes.videoWrapper } ),
$playIcon = $activeSlide.children( '.' + classes.playButton );
let videoType, apiProvider;
$videoContainer.append( $videoWrapper );
$activeSlide.append( $videoContainer );
if ( -1 !== videoURL.indexOf( 'vimeo.com' ) ) {
videoType = 'vimeo';
apiProvider = elementorFrontend.utils.vimeo;
} else if ( videoURL.match( /^(?:https?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com)/ ) ) {
videoType = 'youtube';
apiProvider = elementorFrontend.utils.youtube;
}
const videoID = apiProvider.getVideoIDFromURL( videoURL );
apiProvider.onApiReady( ( apiObject ) => {
if ( 'youtube' === videoType ) {
this.prepareYTVideo( apiObject, videoID, $videoContainer, $videoWrapper, $playIcon );
} else if ( 'vimeo' === videoType ) {
this.prepareVimeoVideo( apiObject, videoID, $videoContainer, $videoWrapper, $playIcon );
}
} );
$playIcon.addClass( classes.playing ).removeClass( classes.hidden );
},
prepareYTVideo: function( YT, videoID, $videoContainer, $videoWrapper, $playIcon ) {
const classes = this.getSettings( 'classes' ),
$videoPlaceholderElement = jQuery( '<div>' );
let startStateCode = YT.PlayerState.PLAYING;
$videoWrapper.append( $videoPlaceholderElement );
// Since version 67, Chrome doesn't fire the `PLAYING` state at start time
if ( window.chrome ) {
startStateCode = YT.PlayerState.UNSTARTED;
}
$videoContainer.addClass( 'elementor-loading' + ' ' + classes.invisible );
this.player = new YT.Player( $videoPlaceholderElement[ 0 ], {
videoId: videoID,
events: {
onReady: () => {
$playIcon.addClass( classes.hidden );
$videoContainer.removeClass( classes.invisible );
this.player.playVideo();
},
onStateChange: ( event ) => {
if ( event.data === startStateCode ) {
$videoContainer.removeClass( 'elementor-loading' + ' ' + classes.invisible );
}
},
},
playerVars: {
controls: 0,
rel: 0,
},
} );
},
prepareVimeoVideo: function( Vimeo, videoId, $videoContainer, $videoWrapper, $playIcon ) {
const classes = this.getSettings( 'classes' ),
vimeoOptions = {
id: videoId,
autoplay: true,
transparent: false,
playsinline: false,
};
this.player = new Vimeo.Player( $videoWrapper, vimeoOptions );
this.player.ready().then( () => {
$playIcon.addClass( classes.hidden );
$videoContainer.removeClass( classes.invisible );
} );
},
setEntranceAnimation: function( animation ) {
animation = animation || elementorFrontend.getCurrentDeviceSetting( this.getSettings( 'modalOptions' ), 'entranceAnimation' );
const $widgetMessage = this.getModal().getElements( 'message' );
if ( this.oldAnimation ) {
$widgetMessage.removeClass( this.oldAnimation );
}
this.oldAnimation = animation;
if ( animation ) {
$widgetMessage.addClass( 'animated ' + animation );
}
},
openSlideshow: function( slideshowID, initialSlideURL ) {
const $allSlideshowLinks = jQuery( this.getSettings( 'selectors.links' ) ).filter( ( index, element ) => {
const $element = jQuery( element );
return slideshowID === element.dataset.elementorLightboxSlideshow && ! $element.parent( '.swiper-slide-duplicate' ).length && ! $element.parents( '.slick-cloned' ).length;
} );
const slides = [];
let initialSlideIndex = 0;
$allSlideshowLinks.each( function() {
const slideVideo = this.dataset.elementorLightboxVideo;
let slideIndex = this.dataset.elementorLightboxIndex;
if ( undefined === slideIndex ) {
slideIndex = $allSlideshowLinks.index( this );
}
if ( initialSlideURL === this.href || ( slideVideo && initialSlideURL === slideVideo ) ) {
initialSlideIndex = slideIndex;
}
const slideData = {
image: this.href,
index: slideIndex,
title: this.dataset.elementorLightboxTitle,
description: this.dataset.elementorLightboxDescription,
};
if ( slideVideo ) {
slideData.video = slideVideo;
}
slides.push( slideData );
} );
slides.sort( ( a, b ) => a.index - b.index );
this.showModal( {
type: 'slideshow',
id: slideshowID,
modalOptions: {
id: 'elementor-lightbox-slideshow-' + slideshowID,
},
slideshow: {
slides: slides,
swiper: {
initialSlide: +initialSlideIndex,
},
},
} );
},
onSlideChange: function() {
this
.getSlide( 'prev' )
.add( this.getSlide( 'next' ) )
.add( this.getSlide( 'active' ) )
.find( '.' + this.getSettings( 'classes.videoWrapper' ) )
.remove();
this.playSlideVideo();
this.updateFooterText();
},
} );

View File

@@ -0,0 +1,172 @@
( function() {
'use strict';
var document = typeof window !== 'undefined' && typeof window.document !== 'undefined' ? window.document : {};
var isCommonjs = typeof module !== 'undefined' && module.exports;
var fn = ( function() {
var val;
var fnMap = [
[
'requestFullscreen',
'exitFullscreen',
'fullscreenElement',
'fullscreenEnabled',
'fullscreenchange',
'fullscreenerror',
],
// New WebKit
[
'webkitRequestFullscreen',
'webkitExitFullscreen',
'webkitFullscreenElement',
'webkitFullscreenEnabled',
'webkitfullscreenchange',
'webkitfullscreenerror',
],
// Old WebKit
[
'webkitRequestFullScreen',
'webkitCancelFullScreen',
'webkitCurrentFullScreenElement',
'webkitCancelFullScreen',
'webkitfullscreenchange',
'webkitfullscreenerror',
],
[
'mozRequestFullScreen',
'mozCancelFullScreen',
'mozFullScreenElement',
'mozFullScreenEnabled',
'mozfullscreenchange',
'mozfullscreenerror',
],
[
'msRequestFullscreen',
'msExitFullscreen',
'msFullscreenElement',
'msFullscreenEnabled',
'MSFullscreenChange',
'MSFullscreenError',
],
];
var i = 0;
var l = fnMap.length;
var ret = {};
for ( ; i < l; i++ ) {
val = fnMap[ i ];
if ( val && val[ 1 ] in document ) {
var valLength = val.length;
for ( i = 0; i < valLength; i++ ) {
ret[ fnMap[ 0 ][ i ] ] = val[ i ];
}
return ret;
}
}
return false;
} )();
var eventNameMap = {
change: fn.fullscreenchange,
error: fn.fullscreenerror,
};
var screenfull = {
request: function( element ) {
return new Promise( function( resolve, reject ) {
var onFullScreenEntered = function() {
this.off( 'change', onFullScreenEntered );
resolve();
}.bind( this );
this.on( 'change', onFullScreenEntered );
element = element || document.documentElement;
Promise.resolve( element[ fn.requestFullscreen ]() ).catch( reject );
}.bind( this ) );
},
exit: function() {
return new Promise( function( resolve, reject ) {
if ( ! this.isFullscreen ) {
resolve();
return;
}
var onFullScreenExit = function() {
this.off( 'change', onFullScreenExit );
resolve();
}.bind( this );
this.on( 'change', onFullScreenExit );
Promise.resolve( document[ fn.exitFullscreen ]() ).catch( reject );
}.bind( this ) );
},
toggle: function( element ) {
return this.isFullscreen ? this.exit() : this.request( element );
},
onchange: function( callback ) {
this.on( 'change', callback );
},
onerror: function( callback ) {
this.on( 'error', callback );
},
on: function( event, callback ) {
var eventName = eventNameMap[ event ];
if ( eventName ) {
document.addEventListener( eventName, callback, false );
}
},
off: function( event, callback ) {
var eventName = eventNameMap[ event ];
if ( eventName ) {
document.removeEventListener( eventName, callback, false );
}
},
raw: fn,
};
if ( ! fn ) {
if ( isCommonjs ) {
module.exports = { isEnabled: false };
} else {
window.screenfull = { isEnabled: false };
}
return;
}
Object.defineProperties( screenfull, {
isFullscreen: {
get: function() {
return Boolean( document[ fn.fullscreenElement ] );
},
},
element: {
enumerable: true,
get: function() {
return document[ fn.fullscreenElement ];
},
},
isEnabled: {
enumerable: true,
get: function() {
// Coerce to boolean in case of old WebKit
return Boolean( document[ fn.fullscreenEnabled ] );
},
},
} );
if ( isCommonjs ) {
module.exports = screenfull;
} else {
window.screenfull = screenfull;
}
} )();

View File

@@ -0,0 +1,120 @@
export default class SwiperBC {
constructor( container, config ) {
this.config = config;
if ( this.config.breakpoints ) {
// The config is passed as a param to allow adjustConfig to be called outside of this wrapper
this.config = this.adjustConfig( config );
}
// In case of a legacy behaviour the constructor should return a new Swiper instance instead of a Promise.
if ( config.legacy ) {
return this.createSwiperInstance( container, this.config );
}
return new Promise( ( resolve ) => {
if ( ! elementorFrontend.config.experimentalFeatures.e_optimized_assets_loading ) {
return resolve( this.createSwiperInstance( container, this.config ) );
}
elementorFrontend.utils.assetsLoader.load( 'script', 'swiper' )
.then( () => resolve( this.createSwiperInstance( container, this.config ) ) );
} );
}
createSwiperInstance( container, config ) {
// The condition should run only once to prevent an additional overwrite of the SwiperSource.
if ( ! SwiperBC.isSwiperLoaded && elementorFrontend.config.experimentalFeatures.e_optimized_assets_loading ) {
SwiperSource = window.Swiper;
SwiperBC.isSwiperLoaded = true;
// Once the SwiperSource has the Swiper lib function, we need to overwrite window.Swiper with the legacySwiper class.
legacySwiper();
}
SwiperSource.prototype.adjustConfig = this.adjustConfig;
return new SwiperSource( container, config );
}
getElementorBreakpointValues() {
const elementorBreakpoints = elementorFrontend.config.responsive.activeBreakpoints,
elementorBreakpointValues = [];
Object.values( elementorBreakpoints ).forEach( ( breakpointConfig ) => {
elementorBreakpointValues.push( breakpointConfig.value );
} );
return elementorBreakpointValues;
}
// Backwards compatibility for Elementor Pro <2.9.0 (old Swiper version - <5.0.0)
// In Swiper 5.0.0 and up, breakpoints changed from acting as max-width to acting as min-width
adjustConfig( config ) {
// Only reverse the breakpoints if the handle param has been defined
if ( ! config.handleElementorBreakpoints ) {
return config;
}
const elementorBreakpoints = elementorFrontend.config.responsive.activeBreakpoints,
elementorBreakpointValues = this.getElementorBreakpointValues();
Object.keys( config.breakpoints ).forEach( ( configBPKey ) => {
const configBPKeyInt = parseInt( configBPKey );
let breakpointToUpdate;
// The `configBPKeyInt + 1` is a BC Fix for Elementor Pro Carousels from 2.8.0-2.8.3 used with Elementor >= 2.9.0
if ( configBPKeyInt === elementorBreakpoints.mobile.value || ( configBPKeyInt + 1 ) === elementorBreakpoints.mobile.value ) {
// This handles the mobile breakpoint. Elementor's default sm breakpoint is never actually used,
// so the mobile breakpoint (md) needs to be handled separately and set to the 0 breakpoint (xs)
breakpointToUpdate = 0;
} else if ( elementorBreakpoints.widescreen && ( configBPKeyInt === elementorBreakpoints.widescreen.value || ( configBPKeyInt + 1 ) === elementorBreakpoints.widescreen.value ) ) {
// Widescreen is a min-width breakpoint. Since in Swiper >5.0 the breakpoint system is min-width based,
// the value we pass to the Swiper instance in this case is the breakpoint from the user, unchanged.
breakpointToUpdate = configBPKeyInt;
} else {
// Find the index of the current config breakpoint in the Elementor Breakpoints array
const currentBPIndexInElementorBPs = elementorBreakpointValues.findIndex( ( elementorBP ) => {
// BC Fix for Elementor Pro Carousels from 2.8.0-2.8.3 used with Elementor >= 2.9.0
return configBPKeyInt === elementorBP || ( configBPKeyInt + 1 ) === elementorBP;
} );
// For all other Swiper config breakpoints, move them one breakpoint down on the breakpoint list,
// according to the array of Elementor's global breakpoints
breakpointToUpdate = elementorBreakpointValues[ currentBPIndexInElementorBPs - 1 ];
}
config.breakpoints[ breakpointToUpdate ] = config.breakpoints[ configBPKey ];
// Then reset the settings in the original breakpoint key to the default values
config.breakpoints[ configBPKey ] = {
slidesPerView: config.slidesPerView,
slidesPerGroup: config.slidesPerGroup ? config.slidesPerGroup : 1,
};
} );
return config;
}
}
// The following code is needed to support Pro version < 3.1.0.
SwiperBC.isSwiperLoaded = false;
// In the legacy behavior, window.Swiper was a class that returns an instance of the Swiper lib function after config adjustments.
function legacySwiper() {
window.Swiper = class {
constructor( container, config ) {
config.legacy = true;
return new SwiperBC( container, config );
}
};
}
let SwiperSource = window.Swiper;
// In case that the Swiper lib exists (meaning not in optimized mode) we overwrite the window.Swiper with a class that supports legacy behavior.
if ( SwiperSource ) {
legacySwiper();
}

View File

@@ -0,0 +1,81 @@
export default class extends elementorModules.ViewModule {
getDefaultSettings() {
return {
selectors: {
links: 'a[href^="%23elementor-action"], a[href^="#elementor-action"]',
},
};
}
bindEvents() {
elementorFrontend.elements.$document.on( 'click', this.getSettings( 'selectors.links' ), this.runLinkAction.bind( this ) );
}
initActions() {
this.actions = {
lightbox: async ( settings ) => {
const lightbox = await elementorFrontend.utils.lightbox;
if ( settings.id ) {
lightbox.openSlideshow( settings.id, settings.url );
} else {
lightbox.showModal( settings );
}
},
};
}
addAction( name, callback ) {
this.actions[ name ] = callback;
}
runAction( url, ...restArgs ) {
url = decodeURIComponent( url );
const actionMatch = url.match( /action=(.+?)&/ ),
settingsMatch = url.match( /settings=(.+)/ );
if ( ! actionMatch ) {
return;
}
const action = this.actions[ actionMatch[ 1 ] ];
if ( ! action ) {
return;
}
let settings = {};
if ( settingsMatch ) {
settings = JSON.parse( atob( settingsMatch[ 1 ] ) );
}
action( settings, ...restArgs );
}
runLinkAction( event ) {
event.preventDefault();
this.runAction( jQuery( event.currentTarget ).attr( 'href' ), event );
}
runHashAction() {
if ( location.hash ) {
this.runAction( location.hash );
}
}
createActionHash( action, settings ) {
// We need to encode the hash tag (#) here, in order to support share links for a variety of providers
return encodeURIComponent( `#elementor-action:action=${ action }&settings=${ btoa( JSON.stringify( settings ) ) }` );
}
onInit() {
super.onInit();
this.initActions();
elementorFrontend.on( 'components:init', this.runHashAction.bind( this ) );
}
}

View File

@@ -0,0 +1,12 @@
// Escape HTML special chars to prevent XSS.
export const escapeHTML = ( str ) => {
const specialChars = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;',
};
return str.replace( /[&<>'"]/g, ( tag ) => specialChars[ tag ] || tag );
};

View File

@@ -0,0 +1,43 @@
export default class BaseLoader extends elementorModules.ViewModule {
getDefaultSettings() {
return {
isInserted: false,
selectors: {
firstScript: 'script:first',
},
};
}
getDefaultElements() {
return {
$firstScript: jQuery( this.getSettings( 'selectors.firstScript' ) ),
};
}
insertAPI() {
this.elements.$firstScript.before( jQuery( '<script>', { src: this.getApiURL() } ) );
this.setSettings( 'isInserted', true );
}
getVideoIDFromURL( url ) {
const videoIDParts = url.match( this.getURLRegex() );
return videoIDParts && videoIDParts[ 1 ];
}
onApiReady( callback ) {
if ( ! this.getSettings( 'isInserted' ) ) {
this.insertAPI();
}
if ( this.isApiLoaded() ) {
callback( this.getApiObject() );
} else {
// If not ready check again by timeout..
setTimeout( () => {
this.onApiReady( callback );
}, 350 );
}
}
}

View File

@@ -0,0 +1,19 @@
import BaseLoader from './base-loader';
export default class VimeoLoader extends BaseLoader {
getApiURL() {
return 'https://player.vimeo.com/api/player.js';
}
getURLRegex() {
return /^(?:https?:\/\/)?(?:www|player\.)?(?:vimeo\.com\/)?(?:video\/|external\/)?(\d+)([^.?&#"'>]?)/;
}
isApiLoaded() {
return window.Vimeo;
}
getApiObject() {
return Vimeo;
}
}

View File

@@ -0,0 +1,19 @@
import BaseLoader from './base-loader';
export default class YoutubeLoader extends BaseLoader {
getApiURL() {
return 'https://www.youtube.com/iframe_api';
}
getURLRegex() {
return /^(?:https?:\/\/)?(?:www\.)?(?:m\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?vi?=|(?:embed|v|vi|user)\/))([^?&"'>]+)/;
}
isApiLoaded() {
return window.YT && YT.loaded;
}
getApiObject() {
return YT;
}
}