636 lines
14 KiB
JavaScript
636 lines
14 KiB
JavaScript
import $ from 'jquery';
|
|
|
|
const { VPData } = window;
|
|
const { settingsPopupGallery } = VPData;
|
|
const templatesSupport = 'content' in document.createElement('template');
|
|
|
|
/*
|
|
* Global Popup Gallery API.
|
|
*/
|
|
const VPPopupAPI = {
|
|
vendor: false,
|
|
|
|
vendors: [
|
|
{
|
|
vendor: 'youtube',
|
|
embedUrl: 'https://www.youtube.com/embed/{{video_id}}?{{params}}',
|
|
pattern:
|
|
/(https?:\/\/)?(www.)?(youtube\.com|youtu\.be|youtube-nocookie\.com)\/(?:embed\/|shorts\/|v\/|watch\?v=|watch\?list=(.*)&v=|watch\?(.*[^&]&)v=)?((\w|-){11})(&list=(\w+)&?)?(.*)/,
|
|
patternIndex: 6,
|
|
params: {
|
|
autoplay: 1,
|
|
autohide: 1,
|
|
fs: 1,
|
|
rel: 0,
|
|
hd: 1,
|
|
wmode: 'transparent',
|
|
enablejsapi: 1,
|
|
html5: 1,
|
|
},
|
|
paramsIndex: 10,
|
|
embedCallback(url, match) {
|
|
let result = false;
|
|
const vendorData = this;
|
|
const videoId =
|
|
match && match[vendorData.patternIndex]
|
|
? match[vendorData.patternIndex]
|
|
: false;
|
|
|
|
if (videoId) {
|
|
const isShorts = /\/shorts\//.test(url);
|
|
|
|
const width = isShorts ? 476 : 1920;
|
|
const height = isShorts ? 847 : 1080;
|
|
|
|
result = VPPopupAPI.embedCallback(
|
|
{
|
|
...vendorData,
|
|
width,
|
|
height,
|
|
},
|
|
videoId,
|
|
url,
|
|
match
|
|
);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
},
|
|
{
|
|
vendor: 'vimeo',
|
|
embedUrl: 'https://player.vimeo.com/video/{{video_id}}?{{params}}',
|
|
pattern:
|
|
/https?:\/\/(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|album\/(\d+)\/video\/|video\/|)(\d+)(?:$|\/|\?)(.*)/,
|
|
patternIndex: 3,
|
|
params: {
|
|
autoplay: 1,
|
|
hd: 1,
|
|
show_title: 1,
|
|
show_byline: 1,
|
|
show_portrait: 0,
|
|
fullscreen: 1,
|
|
},
|
|
paramsIndex: 4,
|
|
},
|
|
],
|
|
|
|
init() {},
|
|
open() {},
|
|
close() {},
|
|
|
|
/**
|
|
* Parse query parameters.
|
|
* Thanks to https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
|
|
*
|
|
* @param {string} query - query string.
|
|
*
|
|
* @return {string}
|
|
*/
|
|
getQueryStringParams(query) {
|
|
return query
|
|
? (/^[?#]/.test(query) ? query.slice(1) : query)
|
|
.split('&')
|
|
.reduce((params, param) => {
|
|
const [key, value] = param.split('=');
|
|
params[key] = value
|
|
? decodeURIComponent(value.replace(/\+/g, ' '))
|
|
: '';
|
|
return params;
|
|
}, {})
|
|
: {};
|
|
},
|
|
|
|
/**
|
|
* Prepare params from parsed URL.
|
|
*
|
|
* @param {Object} match - url match data.
|
|
* @param {Object} vendorData - vendor data.
|
|
*
|
|
* @return {string}
|
|
*/
|
|
prepareParams(match, vendorData) {
|
|
let result = '';
|
|
|
|
// Prepare default params.
|
|
const params = vendorData.params || {};
|
|
|
|
// Parse params from URL.
|
|
if (vendorData.paramsIndex && match && match[vendorData.paramsIndex]) {
|
|
const newParams = VPPopupAPI.getQueryStringParams(
|
|
match[vendorData.paramsIndex]
|
|
);
|
|
|
|
if (newParams && typeof newParams === 'object') {
|
|
Object.keys(newParams).forEach((key) => {
|
|
if (key && newParams[key]) {
|
|
params[key] = newParams[key];
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
if (params && Object.keys(params).length) {
|
|
Object.keys(params).forEach((key) => {
|
|
if (key && params[key]) {
|
|
if (result) {
|
|
result += '&';
|
|
}
|
|
result += `${key}=${params[key]}`;
|
|
}
|
|
});
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Prepare data for embed.
|
|
*
|
|
* @param {Object} vendorData current video vendor data.
|
|
* @param {string} videoId parsed video ID.
|
|
* @param {string} url video URL provided.
|
|
* @param {Object | boolean} match URL match data.
|
|
*
|
|
* @return {Object}
|
|
*/
|
|
embedCallback(vendorData, videoId, url, match = false) {
|
|
let { embedUrl } = vendorData;
|
|
embedUrl = embedUrl.replace(/{{video_id}}/g, videoId);
|
|
embedUrl = embedUrl.replace(/{{video_url}}/g, url);
|
|
embedUrl = embedUrl.replace(
|
|
/{{video_url_encoded}}/g,
|
|
encodeURIComponent(url)
|
|
);
|
|
embedUrl = embedUrl.replace(
|
|
/{{params}}/g,
|
|
match ? VPPopupAPI.prepareParams(match, vendorData) : ''
|
|
);
|
|
|
|
const width = vendorData.width || 1920;
|
|
const height = vendorData.height || 1080;
|
|
|
|
return {
|
|
vendor: vendorData.vendor,
|
|
id: videoId,
|
|
embed: `<iframe width="${width}" height="${height}" src="${embedUrl}" scrolling="no" frameborder="0" allowTransparency="true" allow="accelerometer; autoplay; clipboard-write; fullscreen; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>`,
|
|
embedUrl,
|
|
url,
|
|
width,
|
|
height,
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Parse video URL and return object with data
|
|
*
|
|
* @param {string} url - video url.
|
|
* @param {string} url - optional poster url.
|
|
*
|
|
* @param poster
|
|
* @return {object|boolean} video data
|
|
*/
|
|
parseVideo(url, poster) {
|
|
let result = false;
|
|
|
|
VPPopupAPI.vendors.forEach((vendorData) => {
|
|
if (!result) {
|
|
const match = url.match(vendorData.pattern);
|
|
const videoId =
|
|
match && match[vendorData.patternIndex]
|
|
? match[vendorData.patternIndex]
|
|
: false;
|
|
|
|
if (videoId) {
|
|
// Custom embed callback.
|
|
if (vendorData.embedCallback) {
|
|
result = vendorData.embedCallback(url, match, poster);
|
|
|
|
// Predefined embed callback.
|
|
} else {
|
|
result = VPPopupAPI.embedCallback(
|
|
vendorData,
|
|
videoId,
|
|
url,
|
|
match
|
|
);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Unknown vendor.
|
|
if (!result) {
|
|
result = VPPopupAPI.embedCallback(
|
|
{
|
|
vendor: 'unknown',
|
|
embedUrl: url,
|
|
},
|
|
url,
|
|
url,
|
|
false
|
|
);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Parse gallery item popup data.
|
|
*
|
|
* @param {element} itemElement - gallery item
|
|
*/
|
|
parseItem(itemElement) {
|
|
let result = false;
|
|
|
|
const $dataElement =
|
|
itemElement &&
|
|
itemElement.querySelector('.vp-portfolio__item-popup');
|
|
|
|
if ($dataElement) {
|
|
result = {
|
|
$dataElement,
|
|
$content: $dataElement,
|
|
data: $dataElement.dataset,
|
|
};
|
|
|
|
// Support for <template> tag.
|
|
if (
|
|
templatesSupport &&
|
|
$dataElement.nodeName === 'TEMPLATE' &&
|
|
$dataElement.content
|
|
) {
|
|
result.$content = $dataElement.content;
|
|
}
|
|
|
|
result.$title = result?.$content?.querySelector(
|
|
'.vp-portfolio__item-popup-title'
|
|
);
|
|
result.$description = result?.$content?.querySelector(
|
|
'.vp-portfolio__item-popup-description'
|
|
);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
/**
|
|
* Parse gallery
|
|
*
|
|
* @param {jQuery} $gallery - gallery element.
|
|
*
|
|
* @return {Array} gallery data
|
|
*/
|
|
parseGallery($gallery) {
|
|
const items = [];
|
|
let size;
|
|
let item;
|
|
let video;
|
|
let videoData;
|
|
|
|
// Find all gallery items
|
|
// Skip Swiper slider duplicates.
|
|
// Previously we also used the `:not(.swiper-slide-duplicate-active)`, but it contains a valid first slide.
|
|
$gallery
|
|
.find('.vp-portfolio__item-wrap:not(.swiper-slide-duplicate)')
|
|
.each(function () {
|
|
const itemData = VPPopupAPI.parseItem(this);
|
|
|
|
if (itemData) {
|
|
size = (
|
|
itemData?.data?.vpPopupImgSize || '1920x1080'
|
|
).split('x');
|
|
video = itemData?.data?.vpPopupVideo;
|
|
videoData = false;
|
|
|
|
if (video) {
|
|
videoData = VPPopupAPI.parseVideo(
|
|
video,
|
|
itemData?.data?.vpPopupPoster
|
|
);
|
|
}
|
|
|
|
if (videoData) {
|
|
item = {
|
|
type: 'embed',
|
|
el: this,
|
|
poster: videoData.poster,
|
|
src: videoData.embedUrl,
|
|
embed: videoData.embed,
|
|
width: videoData.width || 1920,
|
|
height: videoData.height || 1080,
|
|
};
|
|
} else {
|
|
// create slide object
|
|
item = {
|
|
type: 'image',
|
|
el: this,
|
|
src: itemData?.data?.vpPopupImg,
|
|
srcset: itemData?.data?.vpPopupImgSrcset,
|
|
width: parseInt(size[0], 10),
|
|
height: parseInt(size[1], 10),
|
|
};
|
|
|
|
const srcSmall =
|
|
itemData?.data?.vpPopupSmImg || item.src;
|
|
if (srcSmall) {
|
|
const smallSize = (
|
|
itemData?.data?.vpPopupSmImgSize ||
|
|
itemData?.data?.vpPopupImgSize ||
|
|
'1920x1080'
|
|
).split('x');
|
|
|
|
item.srcSmall = srcSmall;
|
|
item.srcSmallWidth = parseInt(smallSize[0], 10);
|
|
item.srcSmallHeight = parseInt(smallSize[1], 10);
|
|
}
|
|
|
|
const srcMedium =
|
|
itemData?.data?.vpPopupMdImg || item.src;
|
|
if (srcMedium) {
|
|
const mediumSize = (
|
|
itemData?.data?.vpPopupMdImgSize ||
|
|
itemData?.data?.vpPopupImgSize ||
|
|
'1920x1080'
|
|
).split('x');
|
|
|
|
item.srcMedium = srcMedium;
|
|
item.srcMediumWidth = parseInt(mediumSize[0], 10);
|
|
item.srcMediumHeight = parseInt(mediumSize[1], 10);
|
|
}
|
|
}
|
|
|
|
if (itemData?.$title || itemData?.$description) {
|
|
item.caption =
|
|
(itemData?.$title?.outerHTML || '') +
|
|
(itemData?.$description?.outerHTML || '');
|
|
}
|
|
|
|
items.push(item);
|
|
}
|
|
});
|
|
|
|
return items;
|
|
},
|
|
|
|
/**
|
|
* Try to focus gallery item link.
|
|
* Used when popup gallery is closed.
|
|
*
|
|
* @param {Object} data - data of the current item
|
|
*/
|
|
maybeFocusGalleryItem(data) {
|
|
if (!settingsPopupGallery.restore_focus) {
|
|
return;
|
|
}
|
|
|
|
// Focus native gallery item.
|
|
if (data.linkEl) {
|
|
$(data.linkEl).focus();
|
|
|
|
// Focus Visual Portfolio gallery item.
|
|
} else if (data.el) {
|
|
$(data.el).find('.vp-portfolio__item-img > a').focus();
|
|
}
|
|
},
|
|
};
|
|
|
|
window.VPPopupAPI = VPPopupAPI;
|
|
|
|
// Extend VP class.
|
|
$(document).on('extendClass.vpf', (event, VP) => {
|
|
if (event.namespace !== 'vpf') {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Init popup gallery
|
|
*/
|
|
VP.prototype.initPopupGallery = function () {
|
|
const self = this;
|
|
if (
|
|
!self.options.itemsClickAction ||
|
|
self.options.itemsClickAction === 'url'
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// prevent on preview page
|
|
if (self.isPreview()) {
|
|
return;
|
|
}
|
|
|
|
// click action
|
|
// `a.vp-portfolio__item-overlay` added as fallback for old templates, used in themes.
|
|
self.$item.on(
|
|
`click.vpf-uid-${self.uid}`,
|
|
`
|
|
.vp-portfolio__item a.vp-portfolio__item-meta,
|
|
.vp-portfolio__item .vp-portfolio__item-img > a,
|
|
.vp-portfolio__item .vp-portfolio__item-meta-title > a,
|
|
.vp-portfolio__item a.vp-portfolio__item-overlay
|
|
`,
|
|
function (e) {
|
|
if (e.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
|
|
const $this = $(this);
|
|
let $itemWrap = $this.closest('.vp-portfolio__item-wrap');
|
|
|
|
// Use Swiper data-attribute to support slide duplicates.
|
|
if (
|
|
$itemWrap.hasClass('swiper-slide-duplicate') &&
|
|
$itemWrap.attr('data-swiper-slide-index')
|
|
) {
|
|
$itemWrap = self.$item.find(
|
|
`[data-swiper-slide-index="${$itemWrap.attr(
|
|
'data-swiper-slide-index'
|
|
)}"].swiper-slide:not(.swiper-slide-duplicate)`
|
|
);
|
|
}
|
|
|
|
if (!$itemWrap.find('.vp-portfolio__item-popup').length) {
|
|
return;
|
|
}
|
|
|
|
const items = VPPopupAPI.parseGallery(self.$item);
|
|
let index = -1;
|
|
|
|
// Get gallery item index.
|
|
// We should check all items with gallery data to prevent
|
|
// issue with items and custom URL used.
|
|
items.forEach((item, idx) => {
|
|
if (item.el === $itemWrap[0]) {
|
|
index = idx;
|
|
}
|
|
});
|
|
|
|
// Let's open popup once item index found.
|
|
if (index !== -1) {
|
|
e.preventDefault();
|
|
VPPopupAPI.open(items, index, self);
|
|
}
|
|
}
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Destroy popup gallery
|
|
*/
|
|
VP.prototype.destroyPopupGallery = function () {
|
|
const self = this;
|
|
|
|
if (
|
|
!self.options.itemsClickAction ||
|
|
self.options.itemsClickAction === 'url'
|
|
) {
|
|
return;
|
|
}
|
|
|
|
self.$item.off(`click.vpf-uid-${self.uid}`);
|
|
|
|
self.emitEvent('destroyPopupGallery');
|
|
};
|
|
});
|
|
|
|
// Init.
|
|
$(document).on('init.vpf', (event, self) => {
|
|
if (event.namespace !== 'vpf') {
|
|
return;
|
|
}
|
|
|
|
self.initPopupGallery();
|
|
});
|
|
|
|
// Destroy.
|
|
$(document).on('destroy.vpf', (event, self) => {
|
|
if (event.namespace !== 'vpf') {
|
|
return;
|
|
}
|
|
|
|
self.destroyPopupGallery();
|
|
});
|
|
|
|
// Check if link is image.
|
|
function isLinkImage(link) {
|
|
return /(.png|.jpg|.jpeg|.gif|.tiff|.tif|.jfif|.jpe|.svg|.bmp|.webp)$/.test(
|
|
link.href.toLowerCase().split('?')[0].split('#')[0]
|
|
);
|
|
}
|
|
|
|
// Parse image data from link.
|
|
function parseImgData(link) {
|
|
const $link = $(link);
|
|
let img = link.childNodes[0];
|
|
let caption = $link.next('figcaption');
|
|
|
|
// <noscript> tag used in plugins, that adds lazy loading
|
|
if (img.nodeName === 'NOSCRIPT' && link.childNodes[1]) {
|
|
img = link.childNodes[1];
|
|
}
|
|
|
|
if (!caption.length && $link.parent('.gallery-icon').length) {
|
|
caption = $link.parent('.gallery-icon').next('figcaption');
|
|
}
|
|
|
|
caption = caption.html();
|
|
|
|
if (caption) {
|
|
caption = `<div class="vp-portfolio__item-popup-description">${caption}</div>`;
|
|
}
|
|
|
|
return {
|
|
type: 'image',
|
|
el: img,
|
|
linkEl: link,
|
|
src: link.href,
|
|
caption,
|
|
};
|
|
}
|
|
|
|
/* Popup for default WordPress images */
|
|
if (settingsPopupGallery.enable_on_wordpress_images) {
|
|
$(document).on(
|
|
'click',
|
|
`
|
|
.wp-block-image > a,
|
|
.wp-block-image > figure > a,
|
|
.wp-block-gallery .blocks-gallery-item > figure > a,
|
|
.wp-block-gallery .wp-block-image > a,
|
|
.wp-block-media-text > figure > a,
|
|
.gallery .gallery-icon > a,
|
|
figure.wp-caption > a,
|
|
figure.tiled-gallery__item > a,
|
|
p > a
|
|
`,
|
|
function (e) {
|
|
if (e.isDefaultPrevented()) {
|
|
return;
|
|
}
|
|
|
|
if (!this.childNodes.length) {
|
|
return;
|
|
}
|
|
|
|
let imageNode = this.childNodes[0];
|
|
|
|
// <noscript> tag used in plugins, that adds lazy loading
|
|
if (imageNode.nodeName === 'NOSCRIPT' && this.childNodes[1]) {
|
|
imageNode = this.childNodes[1];
|
|
}
|
|
|
|
// check if child node is <img> or <picture> tag.
|
|
// <picture> tag used in plugins, that adds WebP support
|
|
if (
|
|
imageNode.nodeName !== 'IMG' &&
|
|
imageNode.nodeName !== 'PICTURE'
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// check if link is image.
|
|
if (!isLinkImage(this)) {
|
|
return;
|
|
}
|
|
|
|
e.preventDefault();
|
|
|
|
const $this = $(this);
|
|
const items = [];
|
|
const currentImage = parseImgData(this);
|
|
const $gallery = $this.closest(
|
|
'.wp-block-gallery, .gallery, .tiled-gallery__gallery'
|
|
);
|
|
let activeIndex = 0;
|
|
|
|
// Block gallery, WordPress default gallery, Jetpack gallery.
|
|
if ($gallery.length) {
|
|
const $galleryItems = $gallery.find(
|
|
'.blocks-gallery-item > figure > a, .wp-block-image > a, .gallery-icon > a, figure.tiled-gallery__item > a'
|
|
);
|
|
let i = 0;
|
|
|
|
$galleryItems.each(function () {
|
|
// check if link is image.
|
|
if (isLinkImage(this)) {
|
|
if (this === currentImage.linkEl) {
|
|
activeIndex = i;
|
|
}
|
|
|
|
items.push(parseImgData(this));
|
|
|
|
i += 1;
|
|
}
|
|
});
|
|
|
|
// WordPress gallery.
|
|
} else {
|
|
items.push(currentImage);
|
|
}
|
|
|
|
VPPopupAPI.open(items, activeIndex);
|
|
}
|
|
);
|
|
}
|