first
This commit is contained in:
@ -0,0 +1,42 @@
|
||||
import $ from 'jquery';
|
||||
import { debounce } from 'throttle-debounce';
|
||||
|
||||
let jetpackLazyImagesLoadEvent;
|
||||
try {
|
||||
jetpackLazyImagesLoadEvent = new Event('jetpack-lazy-images-load', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
} catch (e) {
|
||||
jetpackLazyImagesLoadEvent = document.createEvent('Event');
|
||||
jetpackLazyImagesLoadEvent.initEvent(
|
||||
'jetpack-lazy-images-load',
|
||||
true,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// Fix AJAX loaded images.
|
||||
$(document).on('loadedNewItems.vpf', function (event) {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
$('body').get(0).dispatchEvent(jetpackLazyImagesLoadEvent);
|
||||
});
|
||||
|
||||
// Fix masonry reloading when Jetpack images lazy loaded.
|
||||
// https://github.com/Automattic/jetpack/issues/9595
|
||||
//
|
||||
// p.s. it looks like this fix is not working at all in Safari browser.
|
||||
const runReLayout = debounce(200, ($gallery) => {
|
||||
$gallery.vpf('imagesLoaded');
|
||||
});
|
||||
|
||||
$(document.body).on('jetpack-lazy-loaded-image', '.vp-portfolio', function () {
|
||||
const $this = $(this).closest('.vp-portfolio');
|
||||
|
||||
if ($this && $this.length) {
|
||||
runReLayout($this);
|
||||
}
|
||||
});
|
@ -0,0 +1,115 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const { SimpleBar, navigator } = window;
|
||||
const $doc = $(document);
|
||||
|
||||
// Don't run on Mac and mobile devices.
|
||||
const allowScrollbar =
|
||||
!/Mac|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
|
||||
navigator.userAgent
|
||||
);
|
||||
|
||||
if (allowScrollbar && typeof SimpleBar !== 'undefined') {
|
||||
// Extend VP class.
|
||||
$doc.on('extendClass.vpf', (event, VP) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init Simplebar plugin
|
||||
*/
|
||||
VP.prototype.initCustomScrollbar = function () {
|
||||
const self = this;
|
||||
|
||||
self.emitEvent('beforeInitCustomScrollbar');
|
||||
|
||||
self.$items_wrap
|
||||
.find('.vp-portfolio__custom-scrollbar')
|
||||
.each(function () {
|
||||
const instance = SimpleBar.instances.get(this);
|
||||
|
||||
if (!instance) {
|
||||
new SimpleBar(this);
|
||||
}
|
||||
});
|
||||
|
||||
self.emitEvent('initCustomScrollbar');
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy Simplebar plugin
|
||||
*/
|
||||
VP.prototype.destroyCustomScrollbar = function () {
|
||||
const self = this;
|
||||
|
||||
self.$items_wrap
|
||||
.find('[data-simplebar="init"].vp-portfolio__custom-scrollbar')
|
||||
.each(function () {
|
||||
const instance = SimpleBar.instances.get(this);
|
||||
|
||||
if (instance) {
|
||||
instance.unMount();
|
||||
}
|
||||
});
|
||||
|
||||
self.emitEvent('destroyCustomScrollbar');
|
||||
};
|
||||
});
|
||||
|
||||
// Add Items.
|
||||
$doc.on('addItems.vpf', (event, self, $items, removeExisting) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (removeExisting) {
|
||||
self.destroyCustomScrollbar();
|
||||
}
|
||||
|
||||
self.initCustomScrollbar();
|
||||
});
|
||||
|
||||
// Init.
|
||||
$doc.on('init.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.initCustomScrollbar();
|
||||
});
|
||||
|
||||
// Destroy.
|
||||
$doc.on('destroy.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.destroyCustomScrollbar();
|
||||
});
|
||||
|
||||
// Init Swiper duplicated slides scrollbars.
|
||||
$doc.on('initSwiper.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.options.sliderLoop === 'true') {
|
||||
self.initCustomScrollbar();
|
||||
}
|
||||
});
|
||||
|
||||
// Fix Simplebar content size in some themes.
|
||||
// For example, in Astra theme in content with enabled sidebar, Simplebar calculate wrong height automatically.
|
||||
$(() => {
|
||||
$('[data-simplebar="init"].vp-portfolio__custom-scrollbar').each(
|
||||
function () {
|
||||
const instance = SimpleBar.instances.get(this);
|
||||
|
||||
if (instance) {
|
||||
instance.recalculate();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
165
wp-content/plugins/visual-portfolio/assets/js/items-style-fly.js
Normal file
165
wp-content/plugins/visual-portfolio/assets/js/items-style-fly.js
Normal file
@ -0,0 +1,165 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const $wnd = $(window);
|
||||
|
||||
/**
|
||||
* Check if lines cross
|
||||
*
|
||||
* @param {Object} a - first point of the first line
|
||||
* @param {Object} b - second point of the first line
|
||||
* @param {Object} c - first point of the second line
|
||||
* @param {Object} d - second point of the second line
|
||||
*
|
||||
* @return {boolean} cross lines
|
||||
*/
|
||||
function isCrossLine(a, b, c, d) {
|
||||
// Working code #1:
|
||||
//
|
||||
// var common = (b.x - a.x)*(d.y - c.y) - (b.y - a.y)*(d.x - c.x);
|
||||
// if (common === 0) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// var rH = (a.y - c.y)*(d.x - c.x) - (a.x - c.x)*(d.y - c.y);
|
||||
// var sH = (a.y - c.y)*(b.x - a.x) - (a.x - c.x)*(b.y - a.y);
|
||||
//
|
||||
// var r = rH / common;
|
||||
// var s = sH / common;
|
||||
//
|
||||
// return r >= 0 && r <= 1 && s >= 0 && s <= 1;
|
||||
|
||||
// Working code #2:
|
||||
const v1 = (d.x - c.x) * (a.y - c.y) - (d.y - c.y) * (a.x - c.x);
|
||||
const v2 = (d.x - c.x) * (b.y - c.y) - (d.y - c.y) * (b.x - c.x);
|
||||
const v3 = (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
|
||||
const v4 = (b.x - a.x) * (d.y - a.y) - (b.y - a.y) * (d.x - a.x);
|
||||
return v1 * v2 <= 0 && v3 * v4 <= 0;
|
||||
}
|
||||
|
||||
// Init Events.
|
||||
$(document).on('initEvents.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf' || self.options.itemsStyle !== 'fly') {
|
||||
return;
|
||||
}
|
||||
|
||||
const evp = `.vpf-uid-${self.uid}`;
|
||||
|
||||
// determine cursor position
|
||||
let lastCursorPos = {};
|
||||
$wnd.on(`mousemove${evp}`, (e) => {
|
||||
lastCursorPos = {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
};
|
||||
});
|
||||
|
||||
self.$item.on(
|
||||
`mouseenter${evp} mouseleave${evp}`,
|
||||
'.vp-portfolio__item',
|
||||
function (e) {
|
||||
const $this = $(this);
|
||||
const itemRect = $this[0].getBoundingClientRect();
|
||||
const $overlay = $this.find('.vp-portfolio__item-overlay');
|
||||
const enter = e.type === 'mouseenter';
|
||||
let endX = '0%';
|
||||
let endY = '0%';
|
||||
const curCursorPos = {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
};
|
||||
|
||||
// find the corner that placed on cursor path.
|
||||
let isUp = isCrossLine(
|
||||
{ x: itemRect.left, y: itemRect.top },
|
||||
{ x: itemRect.left + itemRect.width, y: itemRect.top },
|
||||
curCursorPos,
|
||||
lastCursorPos
|
||||
);
|
||||
let isDown = isCrossLine(
|
||||
{ x: itemRect.left, y: itemRect.top + itemRect.height },
|
||||
{
|
||||
x: itemRect.left + itemRect.width,
|
||||
y: itemRect.top + itemRect.height,
|
||||
},
|
||||
curCursorPos,
|
||||
lastCursorPos
|
||||
);
|
||||
let isLeft = isCrossLine(
|
||||
{ x: itemRect.left, y: itemRect.top },
|
||||
{ x: itemRect.left, y: itemRect.top + itemRect.height },
|
||||
curCursorPos,
|
||||
lastCursorPos
|
||||
);
|
||||
let isRight = isCrossLine(
|
||||
{ x: itemRect.left + itemRect.width, y: itemRect.top },
|
||||
{
|
||||
x: itemRect.left + itemRect.width,
|
||||
y: itemRect.top + itemRect.height,
|
||||
},
|
||||
curCursorPos,
|
||||
lastCursorPos
|
||||
);
|
||||
|
||||
// Sometimes isCrossLine returned false, so we need to check direction manually (less accurate, but it is not a big problem).
|
||||
if (!isUp && !isDown && !isLeft && !isRight) {
|
||||
const x =
|
||||
(itemRect.width / 2 - curCursorPos.x + itemRect.left) /
|
||||
(itemRect.width / 2);
|
||||
const y =
|
||||
(itemRect.height / 2 - curCursorPos.y + itemRect.top) /
|
||||
(itemRect.height / 2);
|
||||
if (Math.abs(x) > Math.abs(y)) {
|
||||
if (x > 0) {
|
||||
isLeft = true;
|
||||
} else {
|
||||
isRight = true;
|
||||
}
|
||||
} else if (y > 0) {
|
||||
isUp = true;
|
||||
} else {
|
||||
isDown = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isUp) {
|
||||
endY = '-100.1%';
|
||||
} else if (isDown) {
|
||||
endY = '100.1%';
|
||||
} else if (isLeft) {
|
||||
endX = '-100.1%';
|
||||
} else if (isRight) {
|
||||
endX = '100.1%';
|
||||
}
|
||||
|
||||
if (enter) {
|
||||
$overlay.css({
|
||||
transition: 'none',
|
||||
transform: `translateX(${endX}) translateY(${endY}) translateZ(0)`,
|
||||
});
|
||||
// Trigger a reflow, flushing the CSS changes. This need to fix some glithes in Safari and Firefox.
|
||||
// Info here - https://stackoverflow.com/questions/11131875/what-is-the-cleanest-way-to-disable-css-transition-effects-temporarily
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
$overlay[0].offsetHeight;
|
||||
}
|
||||
|
||||
$overlay.css({
|
||||
transition: '.2s transform ease-in-out',
|
||||
transform: `translateX(${enter ? '0%' : endX}) translateY(${
|
||||
enter ? '0%' : endY
|
||||
}) translateZ(0)`,
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Destroy Events.
|
||||
$(document).on('destroyEvents.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf' || self.options.itemsStyle !== 'fly') {
|
||||
return;
|
||||
}
|
||||
|
||||
const evp = `.vpf-uid-${self.uid}`;
|
||||
|
||||
$wnd.off(`mousemove${evp}`);
|
||||
self.$item.off(`mouseenter${evp} mouseleave${evp}`);
|
||||
});
|
183
wp-content/plugins/visual-portfolio/assets/js/layout-grid.js
Normal file
183
wp-content/plugins/visual-portfolio/assets/js/layout-grid.js
Normal file
@ -0,0 +1,183 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const { screenSizes } = window.VPData;
|
||||
|
||||
//
|
||||
// Our custom Grid layout for Isotope.
|
||||
//
|
||||
// * fixes grid items position in FireFox - https://wordpress.org/support/topic/gallery-difference-between-firefox-and-all-other-browsers/
|
||||
//
|
||||
if (
|
||||
typeof window.Isotope !== 'undefined' &&
|
||||
typeof window.Isotope.LayoutMode !== 'undefined'
|
||||
) {
|
||||
const VPRows = window.Isotope.LayoutMode.create('vpRows');
|
||||
const proto = VPRows.prototype;
|
||||
|
||||
proto.measureColumns = function () {
|
||||
// set items, used if measuring first item
|
||||
this.items = this.isotope.filteredItems;
|
||||
|
||||
this.getContainerWidth();
|
||||
|
||||
// if columnWidth is 0, default to outerWidth of first item
|
||||
if (!this.columnWidth) {
|
||||
const firstItem = this.items[0];
|
||||
const firstItemElem = firstItem && firstItem.element;
|
||||
|
||||
// columnWidth fall back to item of first element
|
||||
this.columnWidth =
|
||||
(firstItemElem && window.getSize(firstItemElem).outerWidth) ||
|
||||
// if first elem has no width, default to size of container
|
||||
this.containerWidth;
|
||||
}
|
||||
|
||||
this.columnWidth += this.gutter;
|
||||
|
||||
// calculate columns
|
||||
const containerWidth = this.containerWidth + this.gutter;
|
||||
let cols = containerWidth / this.columnWidth;
|
||||
|
||||
// fix rounding errors, typically with gutters
|
||||
const excess = this.columnWidth - (containerWidth % this.columnWidth);
|
||||
|
||||
// if overshoot is less than a pixel, round up, otherwise floor it
|
||||
const mathMethod = excess && excess < 1 ? 'round' : 'floor';
|
||||
|
||||
cols = Math[mathMethod](cols);
|
||||
this.cols = Math.max(cols, 1);
|
||||
};
|
||||
|
||||
proto.getContainerWidth = function () {
|
||||
// container is parent if fit width
|
||||
const isFitWidth = this._getOption
|
||||
? this._getOption('fitWidth')
|
||||
: false;
|
||||
const container = isFitWidth ? this.element.parentNode : this.element;
|
||||
|
||||
// check that this.size and size are there
|
||||
// IE8 triggers resize on body size change, so they might not be
|
||||
const size = window.getSize(container);
|
||||
this.containerWidth = size && size.innerWidth;
|
||||
};
|
||||
|
||||
proto._resetLayout = function () {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
this.maxY = 0;
|
||||
this.horizontalColIndex = 0;
|
||||
|
||||
this._getMeasurement('columnWidth', 'outerWidth');
|
||||
this._getMeasurement('gutter', 'outerWidth');
|
||||
this.measureColumns();
|
||||
};
|
||||
|
||||
proto._getItemLayoutPosition = function (item) {
|
||||
item.getSize();
|
||||
|
||||
// how many columns does this brick span
|
||||
const remainder = item.size.outerWidth % this.columnWidth;
|
||||
const mathMethod = remainder && remainder < 1 ? 'round' : 'ceil';
|
||||
|
||||
// round if off by 1 pixel, otherwise use ceil
|
||||
let colSpan = Math[mathMethod](item.size.outerWidth / this.columnWidth);
|
||||
colSpan = Math.min(colSpan, this.cols);
|
||||
|
||||
let col = this.horizontalColIndex % this.cols;
|
||||
const isOver = colSpan > 1 && col + colSpan > this.cols;
|
||||
|
||||
// shift to next row if item can't fit on current row
|
||||
col = isOver ? 0 : col;
|
||||
|
||||
// don't let zero-size items take up space
|
||||
const hasSize = item.size.outerWidth && item.size.outerHeight;
|
||||
this.horizontalColIndex = hasSize
|
||||
? col + colSpan
|
||||
: this.horizontalColIndex;
|
||||
|
||||
const itemWidth = item.size.outerWidth + this.gutter;
|
||||
|
||||
// if this element cannot fit in the current row
|
||||
if (this.x !== 0 && this.horizontalColIndex === 1) {
|
||||
this.x = 0;
|
||||
this.y = this.maxY;
|
||||
}
|
||||
|
||||
const position = {
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
};
|
||||
|
||||
this.maxY = Math.max(this.maxY, this.y + item.size.outerHeight);
|
||||
this.x += itemWidth;
|
||||
|
||||
return position;
|
||||
};
|
||||
|
||||
proto._getContainerSize = function () {
|
||||
return { height: this.maxY };
|
||||
};
|
||||
}
|
||||
|
||||
// Init Options.
|
||||
$(document).on('initOptions.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.defaults.gridColumns = 3;
|
||||
|
||||
if (!self.options.gridColumns) {
|
||||
self.options.gridColumns = self.defaults.gridColumns;
|
||||
}
|
||||
if (!self.options.gridImagesAspectRatio) {
|
||||
self.options.gridImagesAspectRatio =
|
||||
self.defaults.gridImagesAspectRatio;
|
||||
}
|
||||
});
|
||||
|
||||
// Init Layout.
|
||||
$(document).on('initLayout.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.options.layout !== 'grid') {
|
||||
return;
|
||||
}
|
||||
|
||||
// columns.
|
||||
self.addStyle('.vp-portfolio__item-wrap', {
|
||||
width: `${100 / self.options.gridColumns}%`,
|
||||
});
|
||||
|
||||
// calculate responsive.
|
||||
let count = self.options.gridColumns - 1;
|
||||
let currentPoint = Math.min(screenSizes.length - 1, count);
|
||||
|
||||
for (; currentPoint >= 0; currentPoint -= 1) {
|
||||
if (count > 0 && typeof screenSizes[currentPoint] !== 'undefined') {
|
||||
self.addStyle(
|
||||
'.vp-portfolio__item-wrap',
|
||||
{
|
||||
width: `${100 / count}%`,
|
||||
},
|
||||
`screen and (max-width: ${screenSizes[currentPoint]}px)`
|
||||
);
|
||||
}
|
||||
count -= 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Change Isotope Layout Mode.
|
||||
$(document).on('beforeInitIsotope.vpf', (event, self, initOptions) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.options.layout !== 'grid' || typeof initOptions !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
initOptions.layoutMode = 'vpRows';
|
||||
});
|
@ -0,0 +1,19 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
// Init Options.
|
||||
$(document).on('initOptions.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.defaults.justifiedRowHeight = 250;
|
||||
self.defaults.justifiedRowHeightTolerance = 0.25;
|
||||
|
||||
if (!self.options.justifiedRowHeight) {
|
||||
self.options.justifiedRowHeight = self.defaults.justifiedRowHeight;
|
||||
}
|
||||
if (!self.options.justifiedRowHeightTolerance) {
|
||||
self.options.justifiedRowHeightTolerance =
|
||||
self.defaults.justifiedRowHeightTolerance;
|
||||
}
|
||||
});
|
@ -0,0 +1,53 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const { screenSizes } = window.VPData;
|
||||
|
||||
// Init Options.
|
||||
$(document).on('initOptions.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.defaults.masonryColumns = 3;
|
||||
|
||||
if (!self.options.masonryColumns) {
|
||||
self.options.masonryColumns = self.defaults.masonryColumns;
|
||||
}
|
||||
if (!self.options.masonryImagesAspectRatio) {
|
||||
self.options.masonryImagesAspectRatio =
|
||||
self.defaults.masonryImagesAspectRatio;
|
||||
}
|
||||
});
|
||||
|
||||
// Init Layout.
|
||||
$(document).on('initLayout.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.options.layout !== 'masonry') {
|
||||
return;
|
||||
}
|
||||
|
||||
// columns.
|
||||
self.addStyle('.vp-portfolio__item-wrap', {
|
||||
width: `${100 / self.options.masonryColumns}%`,
|
||||
});
|
||||
|
||||
// calculate responsive.
|
||||
let count = self.options.masonryColumns - 1;
|
||||
let currentPoint = Math.min(screenSizes.length - 1, count);
|
||||
|
||||
for (; currentPoint >= 0; currentPoint -= 1) {
|
||||
if (count > 0 && typeof screenSizes[currentPoint] !== 'undefined') {
|
||||
self.addStyle(
|
||||
'.vp-portfolio__item-wrap',
|
||||
{
|
||||
width: `${100 / count}%`,
|
||||
},
|
||||
`screen and (max-width: ${screenSizes[currentPoint]}px)`
|
||||
);
|
||||
}
|
||||
count -= 1;
|
||||
}
|
||||
});
|
152
wp-content/plugins/visual-portfolio/assets/js/layout-slider.js
Normal file
152
wp-content/plugins/visual-portfolio/assets/js/layout-slider.js
Normal file
@ -0,0 +1,152 @@
|
||||
import isNumber from 'is-number';
|
||||
import $ from 'jquery';
|
||||
import { throttle } from 'throttle-debounce';
|
||||
|
||||
const { ResizeObserver } = window;
|
||||
|
||||
// Listen for slider width change to calculate dynamic height of images.
|
||||
const dynamicHeightObserver = new ResizeObserver(
|
||||
throttle(100, (entries) => {
|
||||
entries.forEach(({ target }) => {
|
||||
if (target && target.vpf) {
|
||||
const self = target.vpf;
|
||||
|
||||
const calculatedHeight =
|
||||
(self.$item.width() *
|
||||
parseFloat(self.options.sliderItemsHeight)) /
|
||||
100;
|
||||
|
||||
target
|
||||
.querySelector('.vp-portfolio__items-wrap')
|
||||
.style.setProperty(
|
||||
'--vp-layout-slider--auto-items__height',
|
||||
`${calculatedHeight}px`
|
||||
);
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
// Init Layout.
|
||||
$(document).on('initLayout.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.options.layout !== 'slider') {
|
||||
return;
|
||||
}
|
||||
|
||||
['items', 'thumbnails'].forEach((type) => {
|
||||
let itemsHeight =
|
||||
type === 'items'
|
||||
? self.options.sliderItemsHeight
|
||||
: self.options.sliderThumbnailsHeight;
|
||||
|
||||
if (itemsHeight === 'auto') {
|
||||
return;
|
||||
}
|
||||
|
||||
const typeSingle = type.replace(/s$/g, '');
|
||||
let itemsMinHeight =
|
||||
type === 'items' ? self.options.sliderItemsMinHeight : 0;
|
||||
|
||||
itemsHeight = isNumber(itemsHeight) ? `${itemsHeight}px` : itemsHeight;
|
||||
|
||||
// prevent minHeight option in preview, when used 'vh' units.
|
||||
if (itemsMinHeight && self.isPreview() && /vh/.test(itemsMinHeight)) {
|
||||
itemsMinHeight = 0;
|
||||
}
|
||||
|
||||
const itemsPerView =
|
||||
type === 'items'
|
||||
? self.options.sliderSlidesPerView
|
||||
: self.options.sliderThumbnailsPerView;
|
||||
|
||||
if (itemsPerView === 'auto') {
|
||||
// fix fade slider items width.
|
||||
// https://github.com/nk-crew/visual-portfolio/issues/95.
|
||||
let itemsWidth = 'auto';
|
||||
if (type === 'items' && self.options.sliderEffect === 'fade') {
|
||||
itemsWidth = '100%';
|
||||
}
|
||||
|
||||
// Calculate dynamic height.
|
||||
// Previously we tried the pure CSS solution, but there was couple bugs like:
|
||||
// - Classic styles items wrong height
|
||||
// - FireFox wrong images width render
|
||||
if (itemsHeight.indexOf('%') === itemsHeight.length - 1) {
|
||||
dynamicHeightObserver.observe(self.$item[0]);
|
||||
|
||||
// Static height.
|
||||
} else {
|
||||
self.addStyle(`.vp-portfolio__${type}-wrap`, {
|
||||
'--vp-layout-slider--auto-items__height': itemsHeight,
|
||||
});
|
||||
}
|
||||
|
||||
self.addStyle(`.vp-portfolio__${typeSingle}-wrap`, {
|
||||
width: 'auto',
|
||||
});
|
||||
self.addStyle(
|
||||
`.vp-portfolio__${typeSingle} .vp-portfolio__${typeSingle}-img img`,
|
||||
{
|
||||
width: itemsWidth,
|
||||
height: 'var(--vp-layout-slider--auto-items__height)',
|
||||
}
|
||||
);
|
||||
|
||||
// min height.
|
||||
if (itemsMinHeight) {
|
||||
self.addStyle(
|
||||
`.vp-portfolio__${typeSingle} .vp-portfolio__${typeSingle}-img img`,
|
||||
{
|
||||
'min-height': itemsMinHeight,
|
||||
}
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// We have to use this hack with Before to support Dynamic height.
|
||||
// Also, previously we used the `margin-top`,
|
||||
// but it is not working correctly with Items Mininmal Height option.
|
||||
self.addStyle(`.vp-portfolio__${typeSingle}-img-wrap::before`, {
|
||||
'padding-top': itemsHeight,
|
||||
});
|
||||
self.addStyle(`.vp-portfolio__${typeSingle}-img img`, {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
});
|
||||
self.addStyle(`.vp-portfolio__${typeSingle}-img`, {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
});
|
||||
self.addStyle(
|
||||
`.vp-portfolio__${typeSingle} .vp-portfolio__${typeSingle}-img img`,
|
||||
{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}
|
||||
);
|
||||
|
||||
// min height.
|
||||
if (itemsMinHeight) {
|
||||
self.addStyle(`.vp-portfolio__${typeSingle}-img-wrap::before`, {
|
||||
'min-height': itemsMinHeight,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// thumbnails top gap.
|
||||
if (self.options.sliderThumbnailsGap) {
|
||||
self.addStyle('.vp-portfolio__thumbnails-wrap', {
|
||||
'margin-top': `${self.options.sliderThumbnailsGap}px`,
|
||||
});
|
||||
}
|
||||
});
|
179
wp-content/plugins/visual-portfolio/assets/js/layout-tiles.js
Normal file
179
wp-content/plugins/visual-portfolio/assets/js/layout-tiles.js
Normal file
@ -0,0 +1,179 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const { screenSizes } = window.VPData;
|
||||
|
||||
// fix masonry items position for Tiles layout.
|
||||
// https://github.com/nk-crew/visual-portfolio/issues/111
|
||||
if (
|
||||
typeof window.Isotope !== 'undefined' &&
|
||||
typeof window.Isotope.LayoutMode !== 'undefined'
|
||||
) {
|
||||
const MasonryMode = window.Isotope.LayoutMode.modes.masonry;
|
||||
|
||||
if (MasonryMode) {
|
||||
const defaultMeasureColumns = MasonryMode.prototype.measureColumns;
|
||||
MasonryMode.prototype.measureColumns = function () {
|
||||
let runDefault = true;
|
||||
|
||||
// if columnWidth is 0, default to columns count size.
|
||||
if (!this.columnWidth) {
|
||||
const $vp = $(this.element).closest(
|
||||
'.vp-portfolio[data-vp-layout="tiles"]'
|
||||
);
|
||||
|
||||
// change column size for Tiles type only.
|
||||
if ($vp.length && $vp[0].vpf) {
|
||||
this.getContainerWidth();
|
||||
|
||||
const { vpf } = $vp[0];
|
||||
const settings = vpf.getTilesSettings();
|
||||
|
||||
// get columns number
|
||||
let columns = parseInt(settings[0], 10) || 1;
|
||||
|
||||
// calculate responsive.
|
||||
let count = columns - 1;
|
||||
let currentPoint = Math.min(screenSizes.length - 1, count);
|
||||
|
||||
for (; currentPoint >= 0; currentPoint -= 1) {
|
||||
if (
|
||||
count > 0 &&
|
||||
typeof screenSizes[currentPoint] !== 'undefined'
|
||||
) {
|
||||
if (
|
||||
window.innerWidth <= screenSizes[currentPoint]
|
||||
) {
|
||||
columns = count;
|
||||
}
|
||||
}
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
if (columns) {
|
||||
this.columnWidth = this.containerWidth / columns;
|
||||
this.columnWidth += this.gutter;
|
||||
this.cols = columns;
|
||||
runDefault = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (runDefault) {
|
||||
defaultMeasureColumns.call(this);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Extend VP class.
|
||||
$(document).on('extendClass.vpf', (event, VP) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Tiles Layout Settings
|
||||
*
|
||||
* @return {string} tiles layout
|
||||
*/
|
||||
VP.prototype.getTilesSettings = function () {
|
||||
const self = this;
|
||||
|
||||
const layoutArr = self.options.tilesType.split(/[:|]/);
|
||||
|
||||
// remove last empty item
|
||||
if (
|
||||
typeof layoutArr[layoutArr.length - 1] !== 'undefined' &&
|
||||
!layoutArr[layoutArr.length - 1]
|
||||
) {
|
||||
layoutArr.pop();
|
||||
}
|
||||
|
||||
return layoutArr;
|
||||
};
|
||||
});
|
||||
|
||||
// Init Options.
|
||||
$(document).on('initOptions.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.defaults.tilesType = '3|1,1|';
|
||||
|
||||
if (!self.options.tilesType) {
|
||||
self.options.tilesType = self.defaults.tilesType;
|
||||
}
|
||||
});
|
||||
|
||||
// Init Layout.
|
||||
$(document).on('initLayout.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.options.layout !== 'tiles') {
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = self.getTilesSettings();
|
||||
|
||||
// get columns number
|
||||
const columns = parseInt(settings[0], 10) || 1;
|
||||
settings.shift();
|
||||
|
||||
// set columns
|
||||
self.addStyle('.vp-portfolio__item-wrap', {
|
||||
width: `${100 / columns}%`,
|
||||
});
|
||||
|
||||
// set items sizes
|
||||
if (settings && settings.length) {
|
||||
for (let k = 0; k < settings.length; k += 1) {
|
||||
const size = settings[k].split(',');
|
||||
const w = parseFloat(size[0]) || 1;
|
||||
const h = parseFloat(size[1]) || 1;
|
||||
|
||||
let itemSelector = '.vp-portfolio__item-wrap';
|
||||
if (settings.length > 1) {
|
||||
itemSelector += `:nth-of-type(${settings.length}n+${k + 1})`;
|
||||
}
|
||||
|
||||
if (w && w !== 1) {
|
||||
self.addStyle(itemSelector, {
|
||||
width: `${(w * 100) / columns}%`,
|
||||
});
|
||||
}
|
||||
self.addStyle(
|
||||
`${itemSelector} .vp-portfolio__item-img-wrap::before`,
|
||||
{
|
||||
'padding-top': `${h * 100}%`,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// calculate responsive.
|
||||
let count = columns - 1;
|
||||
let currentPoint = Math.min(screenSizes.length - 1, count);
|
||||
|
||||
for (; currentPoint >= 0; currentPoint -= 1) {
|
||||
if (count > 0 && typeof screenSizes[currentPoint] !== 'undefined') {
|
||||
self.addStyle(
|
||||
'.vp-portfolio__item-wrap',
|
||||
{
|
||||
width: `${100 / count}%`,
|
||||
},
|
||||
`screen and (max-width: ${screenSizes[currentPoint]}px)`
|
||||
);
|
||||
self.addStyle(
|
||||
'.vp-portfolio__item-wrap:nth-of-type(n)',
|
||||
{
|
||||
width: `${100 / count}%`,
|
||||
},
|
||||
`screen and (max-width: ${screenSizes[currentPoint]}px)`
|
||||
);
|
||||
}
|
||||
count -= 1;
|
||||
}
|
||||
});
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Visual Portfolio images lazy load fallback for browsers
|
||||
* which does not support CSS :has()
|
||||
*/
|
||||
|
||||
// Lazyloaded - remove preloader images placeholder effect.
|
||||
document.addEventListener('lazybeforeunveil', (e) => {
|
||||
const vpfImgWrapper = e.target.closest(
|
||||
'.vp-portfolio__item-img, .vp-portfolio__thumbnail-img'
|
||||
);
|
||||
|
||||
if (vpfImgWrapper) {
|
||||
vpfImgWrapper.classList.add('vp-has-lazyloading');
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('lazyloaded', (e) => {
|
||||
const vpfImgWrapper = e.target.closest(
|
||||
'.vp-portfolio__item-img, .vp-portfolio__thumbnail-img'
|
||||
);
|
||||
|
||||
if (vpfImgWrapper) {
|
||||
vpfImgWrapper.classList.add('vp-has-lazyloaded');
|
||||
vpfImgWrapper.classList.add('vp-has-lazyloading');
|
||||
}
|
||||
});
|
26
wp-content/plugins/visual-portfolio/assets/js/lazyload.js
Normal file
26
wp-content/plugins/visual-portfolio/assets/js/lazyload.js
Normal file
@ -0,0 +1,26 @@
|
||||
// Recalculate image size if parent is <picture>
|
||||
document.addEventListener('lazybeforesizes', (e) => {
|
||||
// for some reason sometimes e.detail is undefined, so we need to check it.
|
||||
if (!e.detail || !e.detail.width || !e.target) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parent = e.target.closest(':not(picture)');
|
||||
|
||||
if (parent) {
|
||||
e.detail.width = parent.clientWidth || e.detail.width;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Remove <noscript> tag.
|
||||
* Some of optimization plugin make something, that killed our styles with noscript tag.
|
||||
* Related topic: https://wordpress.org/support/topic/visual-portfolio-and-sg-optimizer-dont-play-well/
|
||||
*/
|
||||
document.addEventListener('lazybeforeunveil', (e) => {
|
||||
const prevEl = e.target.previousElementSibling;
|
||||
|
||||
if (prevEl && prevEl.matches('noscript')) {
|
||||
prevEl.remove();
|
||||
}
|
||||
});
|
@ -0,0 +1,12 @@
|
||||
window.lazySizesConfig = window.lazySizesConfig || {};
|
||||
|
||||
window.lazySizesConfig = {
|
||||
...window.lazySizesConfig,
|
||||
lazyClass: 'vp-lazyload',
|
||||
loadedClass: 'vp-lazyloaded',
|
||||
preloadClass: 'vp-lazypreload',
|
||||
loadingClass: 'vp-lazyloading',
|
||||
srcAttr: 'data-src',
|
||||
srcsetAttr: 'data-srcset',
|
||||
sizesAttr: 'data-sizes',
|
||||
};
|
@ -0,0 +1,67 @@
|
||||
(function (window, factory) {
|
||||
const globalInstall = function () {
|
||||
factory(window.lazySizes);
|
||||
window.removeEventListener('lazyunveilread', globalInstall, true);
|
||||
};
|
||||
factory = factory.bind(null, window, window.document);
|
||||
|
||||
if (window.lazySizes) {
|
||||
globalInstall();
|
||||
} else {
|
||||
window.addEventListener('lazyunveilread', globalInstall, true);
|
||||
}
|
||||
})(window, (window, document, lazySizes) => {
|
||||
if (!window.addEventListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
const getCSS = function (elem) {
|
||||
return window.getComputedStyle(elem, null) || {};
|
||||
};
|
||||
|
||||
const objectFitCover = {
|
||||
calculateSize(element, width) {
|
||||
const CSS = getCSS(element);
|
||||
|
||||
if (CSS && CSS.objectFit && CSS.objectFit === 'cover') {
|
||||
const blockHeight = parseInt(
|
||||
element.getAttribute('height'),
|
||||
10
|
||||
);
|
||||
const blockWidth = parseInt(element.getAttribute('width'), 10);
|
||||
|
||||
if (blockHeight) {
|
||||
if (
|
||||
blockWidth / blockHeight >
|
||||
element.clientWidth / element.clientHeight
|
||||
) {
|
||||
width = parseInt(
|
||||
(element.clientHeight * blockWidth) / blockHeight,
|
||||
10
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return width;
|
||||
},
|
||||
};
|
||||
|
||||
lazySizes.objectFitCover = objectFitCover;
|
||||
|
||||
document.addEventListener('lazybeforesizes', (e) => {
|
||||
// for some reason sometimes e.detail is undefined, so we need to check it.
|
||||
if (
|
||||
e.defaultPrevented ||
|
||||
!e.detail ||
|
||||
!e.detail.width ||
|
||||
!e.target ||
|
||||
e.detail.instance !== lazySizes
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element = e.target;
|
||||
e.detail.width = objectFitCover.calculateSize(element, e.detail.width);
|
||||
});
|
||||
});
|
@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Load duplicated Swiper slides to prevent images "blink" effect after swipe.
|
||||
*
|
||||
* @param window
|
||||
* @param factory
|
||||
*/
|
||||
(function (window, factory) {
|
||||
const globalInstall = function () {
|
||||
factory(window.lazySizes);
|
||||
window.removeEventListener('lazyunveilread', globalInstall, true);
|
||||
};
|
||||
factory = factory.bind(null, window, window.document);
|
||||
|
||||
if (window.lazySizes) {
|
||||
globalInstall();
|
||||
} else {
|
||||
window.addEventListener('lazyunveilread', globalInstall, true);
|
||||
}
|
||||
})(window, (window, document, lazySizes) => {
|
||||
if (!window.addEventListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { unveil } = lazySizes.loader;
|
||||
|
||||
const getSiblings = (el, filter) =>
|
||||
[...el.parentNode.children].filter(
|
||||
(child) =>
|
||||
child.nodeType === 1 &&
|
||||
child !== el &&
|
||||
(!filter || child.matches(filter))
|
||||
);
|
||||
|
||||
const swiperDuplicatesLoad = {
|
||||
getSlideData(element) {
|
||||
const $el = element.closest('.swiper-slide');
|
||||
const slideIndex = $el
|
||||
? $el.getAttribute('data-swiper-slide-index')
|
||||
: false;
|
||||
|
||||
return {
|
||||
$el,
|
||||
slideIndex,
|
||||
};
|
||||
},
|
||||
run(element) {
|
||||
const slideData = this.getSlideData(element);
|
||||
|
||||
if (slideData.slideIndex) {
|
||||
const $siblingDuplicates = getSiblings(
|
||||
slideData.$el,
|
||||
`[data-swiper-slide-index="${slideData.slideIndex}"]`
|
||||
);
|
||||
|
||||
$siblingDuplicates.forEach((el) => {
|
||||
// We should also get images in `loading` state, because in some rare situations
|
||||
// duplicated images by default has this class and not displaying correctly.
|
||||
const $images = el.querySelectorAll(
|
||||
'img.vp-lazyload, img.vp-lazyloading'
|
||||
);
|
||||
|
||||
if ($images) {
|
||||
$images.forEach(($img) => {
|
||||
unveil($img);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
lazySizes.swiperDuplicatesLoad = swiperDuplicatesLoad;
|
||||
|
||||
document.addEventListener('lazyloaded', (e) => {
|
||||
// for some reason sometimes e.detail is undefined, so we need to check it.
|
||||
if (
|
||||
e.defaultPrevented ||
|
||||
!e.detail ||
|
||||
e.detail.swiperDuplicatesChecked ||
|
||||
!e.target ||
|
||||
e.detail.instance !== lazySizes
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const element = e.target;
|
||||
e.detail.swiperDuplicatesChecked = swiperDuplicatesLoad.run(element);
|
||||
});
|
||||
});
|
995
wp-content/plugins/visual-portfolio/assets/js/main.js
Normal file
995
wp-content/plugins/visual-portfolio/assets/js/main.js
Normal file
@ -0,0 +1,995 @@
|
||||
import $ from 'jquery';
|
||||
import rafSchd from 'raf-schd';
|
||||
import { throttle } from 'throttle-debounce';
|
||||
|
||||
const { VPData } = window;
|
||||
const { __ } = VPData;
|
||||
const $wnd = $(window);
|
||||
|
||||
/**
|
||||
* Emit Resize Event.
|
||||
*/
|
||||
function windowResizeEmit() {
|
||||
if (typeof window.Event === 'function') {
|
||||
// modern browsers
|
||||
window.dispatchEvent(new window.Event('resize'));
|
||||
} else {
|
||||
// for IE and other old browsers
|
||||
// causes deprecation warning on modern browsers
|
||||
const evt = window.document.createEvent('UIEvents');
|
||||
evt.initUIEvent('resize', true, false, window, 0);
|
||||
window.dispatchEvent(evt);
|
||||
}
|
||||
}
|
||||
|
||||
const visibilityData = {};
|
||||
let shouldCheckVisibility = false;
|
||||
let checkVisibilityTimeout = false;
|
||||
let isFocusVisible = false;
|
||||
|
||||
// fix portfolio inside Tabs and Accordions
|
||||
// check visibility by timer https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom/33456469
|
||||
//
|
||||
// https://github.com/nk-crew/visual-portfolio/issues/11
|
||||
// https://github.com/nk-crew/visual-portfolio/issues/113
|
||||
function checkVisibility() {
|
||||
clearTimeout(checkVisibilityTimeout);
|
||||
|
||||
if (!shouldCheckVisibility) {
|
||||
return;
|
||||
}
|
||||
|
||||
const $items = $('.vp-portfolio__ready');
|
||||
|
||||
if ($items.length) {
|
||||
let isVisibilityChanged = false;
|
||||
|
||||
$items.each(function () {
|
||||
const { vpf } = this;
|
||||
|
||||
if (!vpf) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentState = visibilityData[vpf.uid] || 'none';
|
||||
|
||||
visibilityData[vpf.uid] =
|
||||
this.offsetParent === null ? 'hidden' : 'visible';
|
||||
|
||||
// changed from hidden to visible.
|
||||
if (
|
||||
currentState === 'hidden' &&
|
||||
visibilityData[vpf.uid] === 'visible'
|
||||
) {
|
||||
isVisibilityChanged = true;
|
||||
}
|
||||
});
|
||||
|
||||
// resize, if visibility changed.
|
||||
if (isVisibilityChanged) {
|
||||
windowResizeEmit();
|
||||
}
|
||||
} else {
|
||||
shouldCheckVisibility = false;
|
||||
}
|
||||
|
||||
// run again.
|
||||
checkVisibilityTimeout = setTimeout(checkVisibility, 500);
|
||||
}
|
||||
|
||||
// run check function only after portfolio inited.
|
||||
$(document).on('inited.vpf', (event) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
shouldCheckVisibility = true;
|
||||
|
||||
checkVisibility();
|
||||
});
|
||||
|
||||
/**
|
||||
* If the most recent user interaction was via the keyboard;
|
||||
* and the key press did not include a meta, alt/option, or control key;
|
||||
* then the modality is keyboard. Otherwise, the modality is not keyboard.
|
||||
*/
|
||||
document.addEventListener(
|
||||
'keydown',
|
||||
function (e) {
|
||||
if (e.metaKey || e.altKey || e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
isFocusVisible = true;
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
/**
|
||||
* If at any point a user clicks with a pointing device, ensure that we change
|
||||
* the modality away from keyboard.
|
||||
* This avoids the situation where a user presses a key on an already focused
|
||||
* element, and then clicks on a different element, focusing it with a
|
||||
* pointing device, while we still think we're in keyboard modality.
|
||||
*/
|
||||
document.addEventListener(
|
||||
'mousedown',
|
||||
() => {
|
||||
isFocusVisible = false;
|
||||
},
|
||||
true
|
||||
);
|
||||
document.addEventListener(
|
||||
'pointerdown',
|
||||
() => {
|
||||
isFocusVisible = false;
|
||||
},
|
||||
true
|
||||
);
|
||||
document.addEventListener(
|
||||
'touchstart',
|
||||
() => {
|
||||
isFocusVisible = false;
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
/**
|
||||
* Main VP class
|
||||
*/
|
||||
class VP {
|
||||
constructor($item, userOptions) {
|
||||
const self = this;
|
||||
|
||||
self.$item = $item;
|
||||
|
||||
// get id from class
|
||||
const classes = $item[0].className.split(/\s+/);
|
||||
for (let k = 0; k < classes.length; k += 1) {
|
||||
if (classes[k] && /^vp-uid-/.test(classes[k])) {
|
||||
self.uid = classes[k].replace(/^vp-uid-/, '');
|
||||
}
|
||||
if (classes[k] && /^vp-id-/.test(classes[k])) {
|
||||
self.id = classes[k].replace(/^vp-id-/, '');
|
||||
}
|
||||
}
|
||||
if (!self.uid) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(__.couldnt_retrieve_vp);
|
||||
return;
|
||||
}
|
||||
|
||||
self.href = window.location.href;
|
||||
|
||||
self.$items_wrap = $item.find('.vp-portfolio__items');
|
||||
self.$slider_thumbnails_wrap = $item.find('.vp-portfolio__thumbnails');
|
||||
self.$pagination = $item.find('.vp-portfolio__pagination-wrap');
|
||||
self.$filter = $item.find('.vp-portfolio__filter-wrap');
|
||||
self.$sort = $item.find('.vp-portfolio__sort-wrap');
|
||||
|
||||
// find single filter block.
|
||||
if (self.id) {
|
||||
self.$filter = self.$filter.add(
|
||||
`.vp-single-filter.vp-id-${self.id} .vp-portfolio__filter-wrap`
|
||||
);
|
||||
}
|
||||
|
||||
// find single sort block.
|
||||
if (self.id) {
|
||||
self.$sort = self.$sort.add(
|
||||
`.vp-single-sort.vp-id-${self.id} .vp-portfolio__sort-wrap`
|
||||
);
|
||||
}
|
||||
|
||||
// user options
|
||||
self.userOptions = userOptions;
|
||||
|
||||
self.firstRun = true;
|
||||
|
||||
self.init();
|
||||
}
|
||||
|
||||
// emit event
|
||||
// Example:
|
||||
// $(document).on('init.vpf', function (event, infiniteObject) {
|
||||
// console.log(infiniteObject);
|
||||
// });
|
||||
emitEvent(event, data) {
|
||||
data = data ? [this].concat(data) : [this];
|
||||
this.$item.trigger(`${event}.vpf`, data);
|
||||
this.$item.trigger(`${event}.vpf-uid-${this.uid}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init
|
||||
*/
|
||||
init() {
|
||||
const self = this;
|
||||
|
||||
// destroy if already inited
|
||||
if (!self.firstRun) {
|
||||
self.destroy();
|
||||
}
|
||||
|
||||
self.destroyed = false;
|
||||
|
||||
self.$item.addClass('vp-portfolio__ready');
|
||||
|
||||
// init options
|
||||
self.initOptions();
|
||||
|
||||
// init events
|
||||
self.initEvents();
|
||||
|
||||
// init layout
|
||||
self.initLayout();
|
||||
|
||||
// init custom colors
|
||||
self.initCustomColors();
|
||||
|
||||
self.emitEvent('init');
|
||||
|
||||
if (self.id) {
|
||||
$(`.vp-single-filter.vp-id-${self.id}`)
|
||||
.addClass('vp-single-filter__ready')
|
||||
.parent('.vp-portfolio__layout-elements')
|
||||
.addClass('vp-portfolio__layout-elements__ready');
|
||||
$(`.vp-single-sort.vp-id-${self.id}`)
|
||||
.addClass('vp-single-sort__ready')
|
||||
.parent('.vp-portfolio__layout-elements')
|
||||
.addClass('vp-portfolio__layout-elements__ready');
|
||||
}
|
||||
|
||||
// resized
|
||||
self.resized();
|
||||
|
||||
// images loaded
|
||||
self.imagesLoaded();
|
||||
|
||||
self.emitEvent('inited');
|
||||
|
||||
self.firstRun = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if script loaded in preview.
|
||||
*
|
||||
* @return {boolean} is in preview.
|
||||
*/
|
||||
isPreview() {
|
||||
const self = this;
|
||||
|
||||
return !!self.$item.closest('#vp_preview').length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after resized container.
|
||||
*/
|
||||
resized() {
|
||||
windowResizeEmit();
|
||||
|
||||
this.emitEvent('resized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Images loaded.
|
||||
*/
|
||||
imagesLoaded() {
|
||||
const self = this;
|
||||
|
||||
if (!self.$items_wrap.imagesLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.$items_wrap.imagesLoaded().progress(() => {
|
||||
this.emitEvent('imagesLoaded');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy
|
||||
*/
|
||||
destroy() {
|
||||
const self = this;
|
||||
|
||||
// remove loaded class
|
||||
self.$item.removeClass('vp-portfolio__ready');
|
||||
|
||||
if (self.id) {
|
||||
$(`.vp-single-filter.vp-id-${self.id}`)
|
||||
.removeClass('vp-single-filter__ready')
|
||||
.parent('.vp-portfolio__layout-elements')
|
||||
.removeClass('vp-portfolio__layout-elements__ready');
|
||||
$(`.vp-single-sort.vp-id-${self.id}`)
|
||||
.removeClass('vp-single-sort__ready')
|
||||
.parent('.vp-portfolio__layout-elements')
|
||||
.removeClass('vp-portfolio__layout-elements__ready');
|
||||
}
|
||||
|
||||
// destroy events
|
||||
self.destroyEvents();
|
||||
|
||||
// remove all generated styles
|
||||
self.removeStyle();
|
||||
self.renderStyle();
|
||||
|
||||
self.emitEvent('destroy');
|
||||
|
||||
self.destroyed = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add style to the current portfolio list
|
||||
*
|
||||
* @param {string} selector css selector
|
||||
* @param {string} styles object with styles
|
||||
* @param {string} media string with media query
|
||||
*/
|
||||
addStyle(selector, styles, media) {
|
||||
media = media || '';
|
||||
|
||||
const self = this;
|
||||
const { uid } = self;
|
||||
|
||||
if (!self.stylesList) {
|
||||
self.stylesList = {};
|
||||
}
|
||||
|
||||
if (typeof self.stylesList[uid] === 'undefined') {
|
||||
self.stylesList[uid] = {};
|
||||
}
|
||||
if (typeof self.stylesList[uid][media] === 'undefined') {
|
||||
self.stylesList[uid][media] = {};
|
||||
}
|
||||
if (typeof self.stylesList[uid][media][selector] === 'undefined') {
|
||||
self.stylesList[uid][media][selector] = {};
|
||||
}
|
||||
|
||||
self.stylesList[uid][media][selector] = $.extend(
|
||||
self.stylesList[uid][media][selector],
|
||||
styles
|
||||
);
|
||||
|
||||
self.emitEvent('addStyle', [selector, styles, media, self.stylesList]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove style from the current portfolio list
|
||||
*
|
||||
* @param {string} selector css selector (if not set - removed all styles)
|
||||
* @param {string} styles object with styles
|
||||
* @param {string} media string with media query
|
||||
*/
|
||||
removeStyle(selector, styles, media) {
|
||||
media = media || '';
|
||||
|
||||
const self = this;
|
||||
const { uid } = self;
|
||||
|
||||
if (!self.stylesList) {
|
||||
self.stylesList = {};
|
||||
}
|
||||
|
||||
if (typeof self.stylesList[uid] !== 'undefined' && !selector) {
|
||||
self.stylesList[uid] = {};
|
||||
}
|
||||
|
||||
if (
|
||||
typeof self.stylesList[uid] !== 'undefined' &&
|
||||
typeof self.stylesList[uid][media] !== 'undefined' &&
|
||||
typeof self.stylesList[uid][media][selector] !== 'undefined' &&
|
||||
selector
|
||||
) {
|
||||
delete self.stylesList[uid][media][selector];
|
||||
}
|
||||
|
||||
self.emitEvent('removeStyle', [selector, styles, self.stylesList]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render style for the current portfolio list
|
||||
*/
|
||||
renderStyle() {
|
||||
const self = this;
|
||||
|
||||
// timeout for the case, when styles added one by one
|
||||
const { uid } = self;
|
||||
let stylesString = '';
|
||||
|
||||
if (!self.stylesList) {
|
||||
self.stylesList = {};
|
||||
}
|
||||
|
||||
// create string with styles
|
||||
if (typeof self.stylesList[uid] !== 'undefined') {
|
||||
Object.keys(self.stylesList[uid]).forEach((m) => {
|
||||
// media
|
||||
if (m) {
|
||||
stylesString += `@media ${m} {`;
|
||||
}
|
||||
Object.keys(self.stylesList[uid][m]).forEach((s) => {
|
||||
// selector
|
||||
const selectorParent = `.vp-uid-${uid}`;
|
||||
let selector = `${selectorParent} ${s}`;
|
||||
|
||||
// add parent selector after `,`.
|
||||
selector = selector.replace(
|
||||
/, |,/g,
|
||||
`, ${selectorParent} `
|
||||
);
|
||||
|
||||
stylesString += `${selector} {`;
|
||||
Object.keys(self.stylesList[uid][m][s]).forEach((p) => {
|
||||
// property and value
|
||||
stylesString += `${p}:${self.stylesList[uid][m][s][p]};`;
|
||||
});
|
||||
stylesString += '}';
|
||||
});
|
||||
// media
|
||||
if (m) {
|
||||
stylesString += '}';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// add in style tag
|
||||
let $style = $(`#vp-style-${uid}`);
|
||||
if (!$style.length) {
|
||||
$style = $('<style>')
|
||||
.attr('id', `vp-style-${uid}`)
|
||||
.appendTo('head');
|
||||
}
|
||||
$style.html(stylesString);
|
||||
|
||||
self.emitEvent('renderStyle', [stylesString, self.stylesList, $style]);
|
||||
}
|
||||
|
||||
/**
|
||||
* First char to lower case
|
||||
*
|
||||
* @param {string} str string to transform
|
||||
* @return {string} result string
|
||||
*/
|
||||
firstToLowerCase(str) {
|
||||
return str.substr(0, 1).toLowerCase() + str.substr(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init options
|
||||
*
|
||||
* @param {Object} userOptions user options
|
||||
*/
|
||||
initOptions(userOptions) {
|
||||
const self = this;
|
||||
|
||||
// default options
|
||||
self.defaults = {
|
||||
layout: 'tile',
|
||||
itemsGap: 0,
|
||||
pagination: 'load-more',
|
||||
};
|
||||
|
||||
// new user options
|
||||
if (userOptions) {
|
||||
self.userOptions = userOptions;
|
||||
}
|
||||
|
||||
// prepare data options
|
||||
const dataOptions = self.$item[0].dataset;
|
||||
const pureDataOptions = {};
|
||||
Object.keys(dataOptions).forEach((k) => {
|
||||
if (k && k.substring(0, 2) === 'vp') {
|
||||
pureDataOptions[self.firstToLowerCase(k.substring(2))] =
|
||||
dataOptions[k];
|
||||
}
|
||||
});
|
||||
|
||||
self.options = $.extend(
|
||||
{},
|
||||
self.defaults,
|
||||
pureDataOptions,
|
||||
self.userOptions
|
||||
);
|
||||
|
||||
self.emitEvent('initOptions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Init events
|
||||
*/
|
||||
initEvents() {
|
||||
const self = this;
|
||||
const evp = `.vpf-uid-${self.uid}`;
|
||||
|
||||
// Stretch
|
||||
function stretch() {
|
||||
const rect = self.$item[0].getBoundingClientRect();
|
||||
const { left } = rect;
|
||||
const right = window.innerWidth - rect.right;
|
||||
|
||||
const ml = parseFloat(self.$item.css('margin-left') || 0);
|
||||
const mr = parseFloat(self.$item.css('margin-right') || 0);
|
||||
self.$item.css({
|
||||
marginLeft: ml - left,
|
||||
marginRight: mr - right,
|
||||
maxWidth: 'none',
|
||||
width: 'auto',
|
||||
});
|
||||
}
|
||||
if (self.$item.hasClass('vp-portfolio__stretch') && !self.isPreview()) {
|
||||
$wnd.on(`load${evp} resize${evp} orientationchange${evp}`, () => {
|
||||
stretch();
|
||||
});
|
||||
stretch();
|
||||
}
|
||||
|
||||
// add helper focus class
|
||||
// TODO: change to CSS :has() when will be widely available
|
||||
// @link https://caniuse.com/?search=%3Ahas
|
||||
self.$item.on(`focus${evp}`, '.vp-portfolio__item a', function () {
|
||||
const $item = $(this).closest('.vp-portfolio__item');
|
||||
|
||||
$item.addClass('vp-portfolio__item-focus');
|
||||
|
||||
if (isFocusVisible) {
|
||||
$item.addClass('vp-portfolio__item-focus-visible');
|
||||
}
|
||||
});
|
||||
self.$item.on(`blur${evp}`, '.vp-portfolio__item a', function () {
|
||||
$(this)
|
||||
.closest('.vp-portfolio__item')
|
||||
.removeClass(
|
||||
'vp-portfolio__item-focus vp-portfolio__item-focus-visible'
|
||||
);
|
||||
});
|
||||
|
||||
// on filter click
|
||||
self.$filter.on(
|
||||
`click${evp}`,
|
||||
'.vp-filter .vp-filter__item a',
|
||||
function (e) {
|
||||
e.preventDefault();
|
||||
const $this = $(this);
|
||||
if (!self.loading) {
|
||||
$this
|
||||
.closest('.vp-filter__item')
|
||||
.addClass('vp-filter__item-active')
|
||||
.siblings()
|
||||
.removeClass('vp-filter__item-active');
|
||||
}
|
||||
self.loadNewItems($this.attr('href'), true);
|
||||
}
|
||||
);
|
||||
|
||||
// on sort click
|
||||
self.$sort.on(`click${evp}`, '.vp-sort .vp-sort__item a', function (e) {
|
||||
e.preventDefault();
|
||||
const $this = $(this);
|
||||
if (!self.loading) {
|
||||
$this
|
||||
.closest('.vp-sort__item')
|
||||
.addClass('vp-sort__item-active')
|
||||
.siblings()
|
||||
.removeClass('vp-sort__item-active');
|
||||
}
|
||||
self.loadNewItems($this.attr('href'), true);
|
||||
});
|
||||
|
||||
// on filter/sort select change
|
||||
self.$filter
|
||||
.add(self.$sort)
|
||||
.on(
|
||||
`change${evp}`,
|
||||
'.vp-filter select, .vp-sort select',
|
||||
function () {
|
||||
const $this = $(this);
|
||||
const value = $this.val();
|
||||
const $option = $this.find(`[value="${value}"]`);
|
||||
|
||||
if ($option.length) {
|
||||
self.loadNewItems($option.attr('data-vp-url'), true);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// on pagination click
|
||||
self.$item.on(
|
||||
`click${evp}`,
|
||||
'.vp-pagination .vp-pagination__item a',
|
||||
function (e) {
|
||||
e.preventDefault();
|
||||
const $this = $(this);
|
||||
const $pagination = $this.closest('.vp-pagination');
|
||||
|
||||
if (
|
||||
$pagination.hasClass('vp-pagination__no-more') &&
|
||||
self.options.pagination !== 'paged'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.loadNewItems(
|
||||
$this.attr('href'),
|
||||
self.options.pagination === 'paged'
|
||||
);
|
||||
|
||||
// Scroll to top
|
||||
if (
|
||||
self.options.pagination === 'paged' &&
|
||||
$pagination.hasClass('vp-pagination__scroll-top')
|
||||
) {
|
||||
const $adminBar = $('#wpadminbar');
|
||||
const currentTop =
|
||||
window.pageYOffset ||
|
||||
document.documentElement.scrollTop;
|
||||
let { top } = self.$item.offset();
|
||||
|
||||
// Custom user offset.
|
||||
if ($pagination.attr('data-vp-pagination-scroll-top')) {
|
||||
top -=
|
||||
parseInt(
|
||||
$pagination.attr(
|
||||
'data-vp-pagination-scroll-top'
|
||||
),
|
||||
10
|
||||
) || 0;
|
||||
}
|
||||
|
||||
// Admin bar offset.
|
||||
if (
|
||||
$adminBar.length &&
|
||||
$adminBar.css('position') === 'fixed'
|
||||
) {
|
||||
top -= $adminBar.outerHeight();
|
||||
}
|
||||
|
||||
// Limit max offset.
|
||||
top = Math.max(0, top);
|
||||
|
||||
if (currentTop > top) {
|
||||
window.scrollTo({
|
||||
top,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// on categories of item click
|
||||
self.$item.on(
|
||||
`click${evp}`,
|
||||
'.vp-portfolio__items .vp-portfolio__item-meta-category a',
|
||||
function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
self.loadNewItems($(this).attr('href'), true);
|
||||
}
|
||||
);
|
||||
|
||||
// resized container
|
||||
self.$item.on(`transitionend${evp}`, '.vp-portfolio__items', (e) => {
|
||||
if (e.currentTarget === e.target) {
|
||||
self.resized();
|
||||
}
|
||||
});
|
||||
|
||||
self.emitEvent('initEvents');
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy events
|
||||
*/
|
||||
destroyEvents() {
|
||||
const self = this;
|
||||
const evp = `.vpf-uid-${self.uid}`;
|
||||
|
||||
// destroy click events
|
||||
self.$item.off(evp);
|
||||
self.$filter.off(evp);
|
||||
self.$sort.off(evp);
|
||||
|
||||
// destroy window events
|
||||
$wnd.off(evp);
|
||||
|
||||
self.emitEvent('destroyEvents');
|
||||
}
|
||||
|
||||
/**
|
||||
* Init layout
|
||||
*/
|
||||
initLayout() {
|
||||
const self = this;
|
||||
|
||||
self.emitEvent('initLayout');
|
||||
|
||||
self.renderStyle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init custom color by data attributes:
|
||||
* data-vp-bg-color
|
||||
* data-vp-text-color
|
||||
*/
|
||||
initCustomColors() {
|
||||
const self = this;
|
||||
|
||||
self.$item.find('[data-vp-bg-color]').each(function () {
|
||||
const val = $(this).attr('data-vp-bg-color');
|
||||
self.addStyle(`[data-vp-bg-color="${val}"]`, {
|
||||
'background-color': `${val} !important`,
|
||||
});
|
||||
});
|
||||
|
||||
self.$item.find('[data-vp-text-color]').each(function () {
|
||||
const val = $(this).attr('data-vp-text-color');
|
||||
self.addStyle(`[data-vp-text-color="${val}"]`, {
|
||||
color: `${val} !important`,
|
||||
});
|
||||
});
|
||||
|
||||
self.renderStyle();
|
||||
|
||||
self.emitEvent('initCustomColors');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add New Items
|
||||
*
|
||||
* @param {object|dom|jQuery} $items - elements.
|
||||
* @param {bool} removeExisting - remove existing elements.
|
||||
* @param {Object} $newVP - new visual portfolio jQuery.
|
||||
*/
|
||||
addItems($items, removeExisting, $newVP) {
|
||||
const self = this;
|
||||
|
||||
self.emitEvent('addItems', [$items, removeExisting, $newVP]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove Items
|
||||
*
|
||||
* @param {object|dom|jQuery} $items - elements.
|
||||
*/
|
||||
removeItems($items) {
|
||||
const self = this;
|
||||
|
||||
self.emitEvent('removeItems', [$items]);
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX Load New Items
|
||||
*
|
||||
* @param {string} url - url to request.
|
||||
* @param {bool} removeExisting - remove existing elements.
|
||||
* @param {Function} cb - callback.
|
||||
*/
|
||||
loadNewItems(url, removeExisting, cb) {
|
||||
const self = this;
|
||||
const { randomSeed } = self.options;
|
||||
|
||||
if (
|
||||
(self.loading && typeof self.loading.readyState === 'undefined') ||
|
||||
!url ||
|
||||
self.href === url
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Abort previous AJAX loader to prevent conflict.
|
||||
// We need it mostly for Search feature, because users can type also when already in loading state.
|
||||
if (self.loading && self.loading.readyState && self.loading.abort) {
|
||||
self.loading.abort();
|
||||
}
|
||||
|
||||
const ajaxData = {
|
||||
method: 'POST',
|
||||
url,
|
||||
data: {
|
||||
vpf_ajax_call: true,
|
||||
vpf_random_seed:
|
||||
typeof randomSeed !== 'undefined' ? randomSeed : false,
|
||||
},
|
||||
complete({ responseText }) {
|
||||
self.href = url;
|
||||
self.replaceItems(responseText, removeExisting, cb);
|
||||
},
|
||||
};
|
||||
|
||||
self.loading = true;
|
||||
|
||||
self.$item.addClass('vp-portfolio__loading');
|
||||
|
||||
self.emitEvent('startLoadingNewItems', [url, ajaxData]);
|
||||
|
||||
self.loading = $.ajax(ajaxData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace items to the new loaded using AJAX
|
||||
*
|
||||
* @param {string} content - new page content.
|
||||
* @param {bool} removeExisting - remove existing elements.
|
||||
* @param {Function} cb - callback.
|
||||
*/
|
||||
replaceItems(content, removeExisting, cb) {
|
||||
const self = this;
|
||||
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
// load to invisible container, then append to posts container
|
||||
content = content
|
||||
.replace('<body', '<body><div id="vp-ajax-load-body"')
|
||||
.replace('</body>', '</div></body>');
|
||||
const $body = $(content).filter('#vp-ajax-load-body');
|
||||
|
||||
// find current block on new page
|
||||
const $newVP = $body.find(`.vp-portfolio.vp-uid-${self.uid}`);
|
||||
|
||||
// insert new items
|
||||
if ($newVP.length) {
|
||||
const newItems = $newVP.find('.vp-portfolio__items').html();
|
||||
const nothingFound = $newVP.hasClass('vp-portfolio-not-found');
|
||||
|
||||
// We should clean up notices here, as they may be cloned over and over.
|
||||
self.$item.find('.vp-notice').remove();
|
||||
|
||||
if (nothingFound) {
|
||||
self.$item
|
||||
.find('.vp-portfolio__items-wrap')
|
||||
.before($newVP.find('.vp-notice').clone());
|
||||
self.$item.addClass('vp-portfolio-not-found');
|
||||
} else {
|
||||
self.$item.removeClass('vp-portfolio-not-found');
|
||||
}
|
||||
|
||||
// update filter
|
||||
if (self.$filter.length) {
|
||||
self.$filter.each(function () {
|
||||
const $filter = $(this);
|
||||
let newFilterContent = '';
|
||||
|
||||
if ($filter.parent().hasClass('vp-single-filter')) {
|
||||
newFilterContent = $body
|
||||
.find(
|
||||
`[class="${$filter
|
||||
.parent()
|
||||
.attr('class')
|
||||
.replace(
|
||||
' vp-single-filter__ready',
|
||||
''
|
||||
)}"] .vp-portfolio__filter-wrap`
|
||||
)
|
||||
.html();
|
||||
} else {
|
||||
newFilterContent = $newVP
|
||||
.find('.vp-portfolio__filter-wrap')
|
||||
.html();
|
||||
}
|
||||
|
||||
$filter.html(newFilterContent);
|
||||
});
|
||||
}
|
||||
|
||||
// update sort
|
||||
if (self.$sort.length) {
|
||||
self.$sort.each(function () {
|
||||
const $sort = $(this);
|
||||
let newFilterContent = '';
|
||||
|
||||
if ($sort.parent().hasClass('vp-single-sort')) {
|
||||
newFilterContent = $body
|
||||
.find(
|
||||
`[class="${$sort
|
||||
.parent()
|
||||
.attr('class')
|
||||
.replace(
|
||||
' vp-single-sort__ready',
|
||||
''
|
||||
)}"] .vp-portfolio__sort-wrap`
|
||||
)
|
||||
.html();
|
||||
} else {
|
||||
newFilterContent = $newVP
|
||||
.find('.vp-portfolio__sort-wrap')
|
||||
.html();
|
||||
}
|
||||
|
||||
$sort.html(newFilterContent);
|
||||
});
|
||||
}
|
||||
|
||||
// update pagination
|
||||
if (self.$pagination.length) {
|
||||
self.$pagination.html(
|
||||
$newVP.find('.vp-portfolio__pagination-wrap').html()
|
||||
);
|
||||
}
|
||||
|
||||
self.addItems($(newItems), removeExisting, $newVP);
|
||||
|
||||
self.emitEvent('loadedNewItems', [$newVP, removeExisting, content]);
|
||||
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
// update next page data
|
||||
const nextPageUrl = $newVP.attr('data-vp-next-page-url');
|
||||
self.options.nextPageUrl = nextPageUrl;
|
||||
self.$item.attr('data-vp-next-page-url', nextPageUrl);
|
||||
|
||||
self.$item.removeClass('vp-portfolio__loading');
|
||||
|
||||
self.loading = false;
|
||||
|
||||
self.emitEvent('endLoadingNewItems');
|
||||
|
||||
// images loaded
|
||||
self.imagesLoaded();
|
||||
|
||||
// init custom colors
|
||||
self.initCustomColors();
|
||||
}
|
||||
}
|
||||
|
||||
// extend VP object.
|
||||
$(document).trigger('extendClass.vpf', [VP]);
|
||||
|
||||
// global definition
|
||||
const plugin = function (options, ...args) {
|
||||
let ret;
|
||||
|
||||
this.each(function () {
|
||||
if (typeof ret !== 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof options === 'object' || typeof options === 'undefined') {
|
||||
if (!this.vpf) {
|
||||
this.vpf = new VP($(this), options);
|
||||
}
|
||||
} else if (this.vpf) {
|
||||
ret = this.vpf[options](...args);
|
||||
}
|
||||
});
|
||||
|
||||
return typeof ret !== 'undefined' ? ret : this;
|
||||
};
|
||||
plugin.constructor = VP;
|
||||
|
||||
// no conflict
|
||||
const oldPlugin = $.fn.vpf;
|
||||
$.fn.vpf = plugin;
|
||||
$.fn.vpf.noConflict = function () {
|
||||
$.fn.vpf = oldPlugin;
|
||||
return this;
|
||||
};
|
||||
|
||||
// initialization
|
||||
$(() => {
|
||||
$('.vp-portfolio').vpf();
|
||||
});
|
||||
|
||||
const throttledInit = throttle(
|
||||
200,
|
||||
rafSchd(() => {
|
||||
$('.vp-portfolio:not(.vp-portfolio__ready)').vpf();
|
||||
})
|
||||
);
|
||||
if (window.MutationObserver) {
|
||||
new window.MutationObserver(throttledInit).observe(
|
||||
document.documentElement,
|
||||
{
|
||||
childList: true,
|
||||
subtree: true,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
$(document).on('DOMContentLoaded DOMNodeInserted load', () => {
|
||||
throttledInit();
|
||||
});
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import $ from 'jquery';
|
||||
import rafSchd from 'raf-schd';
|
||||
import { throttle } from 'throttle-debounce';
|
||||
|
||||
const $wnd = $(window);
|
||||
|
||||
// Init infinite scroll pagination.
|
||||
$(document).on('initEvents.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf' || self.options.pagination !== 'infinite') {
|
||||
return;
|
||||
}
|
||||
|
||||
const evp = `.vpf-uid-${self.uid}`;
|
||||
const scrollThreshold = 400;
|
||||
let visibilityCheckBusy = false;
|
||||
|
||||
function checkVisibilityAndLoad() {
|
||||
if (visibilityCheckBusy || !self.options.nextPageUrl) {
|
||||
return;
|
||||
}
|
||||
visibilityCheckBusy = true;
|
||||
|
||||
const rect = self.$item[0].getBoundingClientRect();
|
||||
|
||||
if (
|
||||
rect.bottom > 0 &&
|
||||
rect.bottom - scrollThreshold <= window.innerHeight
|
||||
) {
|
||||
self.loadNewItems(self.options.nextPageUrl, false, () => {
|
||||
setTimeout(() => {
|
||||
visibilityCheckBusy = false;
|
||||
checkVisibilityAndLoad();
|
||||
}, 300);
|
||||
});
|
||||
} else {
|
||||
visibilityCheckBusy = false;
|
||||
}
|
||||
}
|
||||
|
||||
checkVisibilityAndLoad();
|
||||
|
||||
$wnd.on(
|
||||
`load${evp} scroll${evp} resize${evp} orientationchange${evp}`,
|
||||
throttle(
|
||||
150,
|
||||
rafSchd(() => {
|
||||
checkVisibilityAndLoad();
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
@ -0,0 +1,46 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const { ResizeObserver } = window;
|
||||
|
||||
function setImgWidth($el) {
|
||||
if ($el && $el.height > 1) {
|
||||
$el.style.width = `${$el.height}px`;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to use resize observer because for some reason in the Preview
|
||||
// and on some mobile devices image height is 1px.
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
entries.forEach(({ target }) => {
|
||||
if (target) {
|
||||
setImgWidth(target);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Init minimal paged pagination.
|
||||
$(document).on('init.vpf loadedNewItems.vpf', (event, self) => {
|
||||
if (
|
||||
event.namespace !== 'vpf' ||
|
||||
self.options.pagination !== 'paged' ||
|
||||
!self.$pagination.children('.vp-pagination__style-minimal').length
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hack used in Paged active item to make circle using hidden <img>.
|
||||
// See styles for <img> tag in /templates/pagination/style.scss
|
||||
const $activeItem = self.$pagination.find('.vp-pagination__item-active');
|
||||
let $img = $activeItem.find('img');
|
||||
|
||||
if (!$img.length) {
|
||||
$img = $(
|
||||
'<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="">'
|
||||
);
|
||||
|
||||
resizeObserver.observe($img[0]);
|
||||
$activeItem.prepend($img);
|
||||
|
||||
setImgWidth($img[0]);
|
||||
}
|
||||
});
|
209
wp-content/plugins/visual-portfolio/assets/js/plugin-fancybox.js
Normal file
209
wp-content/plugins/visual-portfolio/assets/js/plugin-fancybox.js
Normal file
@ -0,0 +1,209 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const { VPData, VPPopupAPI } = window;
|
||||
const { __, settingsPopupGallery } = VPData;
|
||||
const $doc = $(document);
|
||||
const $window = $(window);
|
||||
|
||||
if (typeof $.fancybox !== 'undefined' && VPPopupAPI) {
|
||||
let fancyboxInstance;
|
||||
|
||||
// Extend Popup API.
|
||||
VPPopupAPI.vendor = 'fancybox';
|
||||
VPPopupAPI.open = function (items, index, self) {
|
||||
const finalItems = [];
|
||||
|
||||
// prepare items for fancybox api.
|
||||
items.forEach((item) => {
|
||||
if (item.type === 'embed' && item.src) {
|
||||
finalItems.push({
|
||||
type: 'iframe',
|
||||
src: item.src,
|
||||
opts: {
|
||||
width: item.width,
|
||||
height: item.height,
|
||||
caption: item.caption,
|
||||
},
|
||||
});
|
||||
} else if (item.type === 'embed' && item.embed) {
|
||||
finalItems.push({
|
||||
type: 'html',
|
||||
src: item.embed,
|
||||
opts: {
|
||||
width: item.width,
|
||||
height: item.height,
|
||||
caption: item.caption,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
finalItems.push({
|
||||
type: 'image',
|
||||
src: item.src,
|
||||
el: item.el,
|
||||
opts: {
|
||||
width: item.width,
|
||||
height: item.height,
|
||||
srcset: item.srcset,
|
||||
caption: item.caption,
|
||||
thumb: item.srcSmall,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const buttons = [];
|
||||
if (settingsPopupGallery.show_zoom_button) {
|
||||
buttons.push('zoom');
|
||||
}
|
||||
if (settingsPopupGallery.show_fullscreen_button) {
|
||||
buttons.push('fullScreen');
|
||||
}
|
||||
if (settingsPopupGallery.show_slideshow) {
|
||||
buttons.push('slideShow');
|
||||
}
|
||||
if (settingsPopupGallery.show_thumbs) {
|
||||
buttons.push('thumbs');
|
||||
}
|
||||
if (settingsPopupGallery.show_share_button) {
|
||||
buttons.push('share');
|
||||
}
|
||||
if (settingsPopupGallery.show_download_button) {
|
||||
buttons.push('download');
|
||||
}
|
||||
if (settingsPopupGallery.show_close_button) {
|
||||
buttons.push('close');
|
||||
}
|
||||
|
||||
// define options
|
||||
const options = {
|
||||
// Close existing modals
|
||||
// Set this to false if you do not need to stack multiple instances
|
||||
closeExisting: true,
|
||||
|
||||
// Enable infinite gallery navigation
|
||||
loop: true,
|
||||
|
||||
// Should display navigation arrows at the screen edges
|
||||
arrows: settingsPopupGallery.show_arrows,
|
||||
|
||||
// Should display counter at the top left corner
|
||||
infobar: settingsPopupGallery.show_counter,
|
||||
|
||||
// Should display close button (using `btnTpl.smallBtn` template) over the content
|
||||
// Can be true, false, "auto"
|
||||
// If "auto" - will be automatically enabled for "html", "inline" or "ajax" items
|
||||
smallBtn: false,
|
||||
|
||||
// Should display toolbar (buttons at the top)
|
||||
// Can be true, false, "auto"
|
||||
// If "auto" - will be automatically hidden if "smallBtn" is enabled
|
||||
toolbar: 'auto',
|
||||
|
||||
// What buttons should appear in the top right corner.
|
||||
// Buttons will be created using templates from `btnTpl` option
|
||||
// and they will be placed into toolbar (class="fancybox-toolbar"` element)
|
||||
buttons,
|
||||
|
||||
// Custom CSS class for layout
|
||||
baseClass: 'vp-fancybox',
|
||||
|
||||
// Hide browser vertical scrollbars; use at your own risk
|
||||
hideScrollbar: true,
|
||||
|
||||
// Use mousewheel to navigate gallery
|
||||
// If 'auto' - enabled for images only
|
||||
wheel: false,
|
||||
|
||||
// Clicked on the content
|
||||
clickContent(current) {
|
||||
return current.type === 'image' &&
|
||||
settingsPopupGallery.click_to_zoom
|
||||
? 'zoom'
|
||||
: false;
|
||||
},
|
||||
|
||||
lang: 'wordpress',
|
||||
i18n: {
|
||||
wordpress: {
|
||||
CLOSE: __.fancybox_close,
|
||||
NEXT: __.fancybox_next,
|
||||
PREV: __.fancybox_prev,
|
||||
ERROR: __.fancybox_error,
|
||||
PLAY_START: __.fancybox_play_start,
|
||||
PLAY_STOP: __.fancybox_play_stop,
|
||||
FULL_SCREEN: __.fancybox_full_screen,
|
||||
THUMBS: __.fancybox_thumbs,
|
||||
DOWNLOAD: __.fancybox_download,
|
||||
SHARE: __.fancybox_share,
|
||||
ZOOM: __.fancybox_zoom,
|
||||
},
|
||||
},
|
||||
|
||||
beforeClose() {
|
||||
const currentItemData = items[fancyboxInstance.currIndex];
|
||||
|
||||
if (currentItemData) {
|
||||
VPPopupAPI.maybeFocusGalleryItem(currentItemData);
|
||||
}
|
||||
|
||||
if (self) {
|
||||
self.emitEvent('beforeCloseFancybox', [
|
||||
options,
|
||||
items,
|
||||
fancyboxInstance,
|
||||
]);
|
||||
}
|
||||
|
||||
fancyboxInstance = false;
|
||||
},
|
||||
beforeShow(e, instance) {
|
||||
if (self) {
|
||||
self.emitEvent('beforeShowFancybox', [e, instance]);
|
||||
}
|
||||
},
|
||||
afterShow(e, instance) {
|
||||
if (self) {
|
||||
self.emitEvent('afterShowFancybox', [e, instance]);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if (self) {
|
||||
self.emitEvent('beforeInitFancybox', [options, finalItems, index]);
|
||||
}
|
||||
|
||||
// Disable Loop if only 1 item in gallery.
|
||||
// We need this because Fancybox still let us scroll gallery using keyboard.
|
||||
if (items.length === 1) {
|
||||
options.loop = false;
|
||||
}
|
||||
|
||||
// Start new fancybox instance
|
||||
fancyboxInstance = $.fancybox.open(finalItems, options, index);
|
||||
|
||||
if (self) {
|
||||
self.emitEvent('initFancybox', [
|
||||
options,
|
||||
finalItems,
|
||||
index,
|
||||
fancyboxInstance,
|
||||
]);
|
||||
}
|
||||
};
|
||||
VPPopupAPI.close = function () {
|
||||
if (fancyboxInstance) {
|
||||
fancyboxInstance.close();
|
||||
fancyboxInstance = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Fix zoom image sizes attribute.
|
||||
// https://wordpress.org/support/topic/blurry-zoom-images/
|
||||
$doc.on('transitionend', '.fancybox-content', function () {
|
||||
const $img = $(this).find('.fancybox-image[sizes]');
|
||||
|
||||
const sizes = `${Math.round(100 * ($img.width() / $window.width()))}vw`;
|
||||
|
||||
$img.attr('sizes', sizes);
|
||||
});
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
// Extend VP class.
|
||||
$(document).on('extendClass.vpf', (event, VP) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init fjGallery plugin
|
||||
*
|
||||
* @param {mixed} options - gallery options.
|
||||
* @param {mixed} additional - additional args.
|
||||
*/
|
||||
VP.prototype.initFjGallery = function (options = false, additional = null) {
|
||||
const self = this;
|
||||
|
||||
if (self.$items_wrap.fjGallery && self.options.layout === 'justified') {
|
||||
const initOptions =
|
||||
options !== false
|
||||
? options
|
||||
: {
|
||||
gutter: {
|
||||
horizontal:
|
||||
parseFloat(self.options.itemsGap) || 0,
|
||||
vertical:
|
||||
self.options.itemsGapVertical !== ''
|
||||
? parseFloat(
|
||||
self.options.itemsGapVertical
|
||||
) || 0
|
||||
: parseFloat(self.options.itemsGap) ||
|
||||
0,
|
||||
},
|
||||
rowHeight:
|
||||
parseFloat(self.options.justifiedRowHeight) ||
|
||||
200,
|
||||
maxRowsCount:
|
||||
parseInt(
|
||||
self.options.justifiedMaxRowsCount,
|
||||
10
|
||||
) || 0,
|
||||
lastRow: self.options.justifiedLastRow || 'left',
|
||||
rowHeightTolerance:
|
||||
parseFloat(
|
||||
self.options.justifiedRowHeightTolerance
|
||||
) || 0,
|
||||
calculateItemsHeight: true,
|
||||
itemSelector: '.vp-portfolio__item-wrap',
|
||||
imageSelector: '.vp-portfolio__item-img img',
|
||||
transitionDuration: '0.3s',
|
||||
};
|
||||
|
||||
if (initOptions.maxRowsCount === 0) {
|
||||
initOptions.maxRowsCount = Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
self.emitEvent('beforeInitFjGallery', [initOptions, additional]);
|
||||
|
||||
self.$items_wrap.fjGallery(initOptions, additional);
|
||||
|
||||
self.emitEvent('initFjGallery', [initOptions, additional]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy fjGallery plugin
|
||||
*/
|
||||
VP.prototype.destroyFjGallery = function () {
|
||||
const self = this;
|
||||
const fjGallery = self.$items_wrap.data('fjGallery');
|
||||
|
||||
if (fjGallery) {
|
||||
self.$items_wrap.fjGallery('destroy');
|
||||
|
||||
self.emitEvent('destroyFjGallery');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Add Items.
|
||||
$(document).on('addItems.vpf', (event, self, $items, removeExisting) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
const fjGallery = self.$items_wrap.data('fjGallery');
|
||||
|
||||
if (!fjGallery) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (removeExisting) {
|
||||
self.destroyFjGallery();
|
||||
self.$items_wrap.find('.vp-portfolio__item-wrap').remove();
|
||||
self.$items_wrap.prepend($items);
|
||||
self.initFjGallery();
|
||||
} else {
|
||||
self.$items_wrap.append($items);
|
||||
self.initFjGallery('appendImages', $items);
|
||||
}
|
||||
});
|
||||
|
||||
// Init.
|
||||
$(document).on('init.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.initFjGallery();
|
||||
});
|
||||
|
||||
// Images Loaded.
|
||||
$(document).on('imagesLoaded.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
// sometimes on iOs images failed to calculate positions, so we need this imagesLoaded event.
|
||||
// related issue: https://github.com/nk-crew/visual-portfolio/issues/55
|
||||
self.initFjGallery();
|
||||
});
|
||||
|
||||
// Destroy.
|
||||
$(document).on('destroy.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.destroyFjGallery();
|
||||
});
|
206
wp-content/plugins/visual-portfolio/assets/js/plugin-isotope.js
Normal file
206
wp-content/plugins/visual-portfolio/assets/js/plugin-isotope.js
Normal file
@ -0,0 +1,206 @@
|
||||
import $ from 'jquery';
|
||||
import rafSchd from 'raf-schd';
|
||||
import { debounce, throttle } from 'throttle-debounce';
|
||||
|
||||
const { getComputedStyle } = window;
|
||||
const $wnd = $(window);
|
||||
const $doc = $(document);
|
||||
|
||||
const SUPPORTED_LAYOUTS = ['tiles', 'masonry', 'grid'];
|
||||
|
||||
// Extend VP class.
|
||||
$doc.on('extendClass.vpf', (event, VP) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init Isotope
|
||||
* TODO: Check one of these scripts as alternative
|
||||
* - https://github.com/haltu/muuri
|
||||
* - https://github.com/Vestride/Shuffle
|
||||
* - https://github.com/patrickkunka/mixitup
|
||||
*
|
||||
* @param {Object} options isotope options
|
||||
*/
|
||||
VP.prototype.initIsotope = function (options) {
|
||||
const self = this;
|
||||
|
||||
if (
|
||||
self.$items_wrap.isotope &&
|
||||
SUPPORTED_LAYOUTS.includes(self.options.layout)
|
||||
) {
|
||||
const isRtl =
|
||||
getComputedStyle(self.$items_wrap[0]).direction === 'rtl';
|
||||
|
||||
const initOptions = options || {
|
||||
itemSelector: '.vp-portfolio__item-wrap',
|
||||
layoutMode: 'masonry',
|
||||
// masonry: {
|
||||
// horizontalOrder: true
|
||||
// },
|
||||
transitionDuration: '0.3s',
|
||||
percentPosition: true,
|
||||
originLeft: !isRtl,
|
||||
|
||||
// See `initEvents.vpf` event why we need this option disabled.
|
||||
resize: false,
|
||||
};
|
||||
|
||||
self.emitEvent('beforeInitIsotope', [initOptions]);
|
||||
|
||||
self.$items_wrap.isotope(initOptions);
|
||||
|
||||
self.emitEvent('initIsotope', [initOptions]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy Isotope
|
||||
*/
|
||||
VP.prototype.destroyIsotope = function () {
|
||||
const self = this;
|
||||
const isotope = self.$items_wrap.data('isotope');
|
||||
|
||||
if (isotope) {
|
||||
self.$items_wrap.isotope('destroy');
|
||||
|
||||
self.emitEvent('destroyIsotope');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Add Items.
|
||||
$doc.on('addItems.vpf', (event, self, $items, removeExisting) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
const isotope = self.$items_wrap.data('isotope');
|
||||
|
||||
if (!isotope) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (removeExisting) {
|
||||
const $existing = self.$items_wrap.find('.vp-portfolio__item-wrap');
|
||||
self.$items_wrap.isotope('remove', $existing);
|
||||
|
||||
// we need to prepend items when remove existing just because Tiles layout have troubles with appending and removing items
|
||||
self.$items_wrap.prepend($items).isotope('prepended', $items);
|
||||
} else {
|
||||
self.$items_wrap.append($items).isotope('appended', $items);
|
||||
}
|
||||
|
||||
// idk why, but with timeout isotope recalculate all items fine.
|
||||
setTimeout(() => {
|
||||
self.initIsotope('layout');
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// Remove Items.
|
||||
$doc.on('removeItems.vpf', (event, self, $items) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
const isotope = self.$items_wrap.data('isotope');
|
||||
|
||||
if (!isotope) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.$items_wrap.isotope('remove', $items);
|
||||
});
|
||||
|
||||
// Init.
|
||||
$doc.on('init.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.initIsotope();
|
||||
});
|
||||
|
||||
// Images Loaded.
|
||||
$doc.on('imagesLoaded.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
// sometimes on iOs images failed to calculate positions, so we need this imagesLoaded event.
|
||||
// related issue: https://github.com/nk-crew/visual-portfolio/issues/55
|
||||
self.initIsotope('layout');
|
||||
});
|
||||
|
||||
// Destroy.
|
||||
$doc.on('destroy.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.destroyIsotope();
|
||||
});
|
||||
|
||||
// Init events.
|
||||
$doc.on('initEvents.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to resize isotope manually, since the native relayout
|
||||
// is not working properly, when container size is not changed
|
||||
// but items sizes are changed in CSS. For some reason Isotope don't relayout it.
|
||||
if (
|
||||
self.$items_wrap.isotope &&
|
||||
SUPPORTED_LAYOUTS.includes(self.options.layout)
|
||||
) {
|
||||
const evp = `.vpf-uid-${self.uid}`;
|
||||
|
||||
$wnd.on(
|
||||
`resize${evp}`,
|
||||
throttle(
|
||||
100,
|
||||
rafSchd(() => {
|
||||
self.initIsotope('layout');
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Destroy events.
|
||||
$doc.on('destroyEvents.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SUPPORTED_LAYOUTS.includes(self.options.layout)) {
|
||||
const evp = `.vpf-uid-${self.uid}`;
|
||||
|
||||
$wnd.off(`resize${evp}`);
|
||||
}
|
||||
});
|
||||
|
||||
// WPBakery Page Builder fullwidth row fix.
|
||||
$doc.on(
|
||||
'vc-full-width-row',
|
||||
debounce(
|
||||
150,
|
||||
rafSchd((event, el) => {
|
||||
$(el)
|
||||
.find('.vp-portfolio')
|
||||
.each(function () {
|
||||
if (!this.vpf || !this.vpf.initIsotope) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isotope = this.vpf.$items_wrap.data('isotope');
|
||||
|
||||
if (isotope) {
|
||||
this.vpf.initIsotope('layout');
|
||||
}
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
@ -0,0 +1,515 @@
|
||||
import isNumber from 'is-number';
|
||||
import $ from 'jquery';
|
||||
|
||||
const {
|
||||
Image,
|
||||
VPData,
|
||||
VPPopupAPI,
|
||||
PhotoSwipe,
|
||||
PhotoSwipeUI_Default: PhotoSwipeUIDefault,
|
||||
} = window;
|
||||
const { __, settingsPopupGallery } = VPData;
|
||||
|
||||
function resizeVideo(data, curItem) {
|
||||
if (typeof curItem === 'undefined') {
|
||||
if (data && data.itemHolders.length) {
|
||||
data.itemHolders.forEach((val) => {
|
||||
if (val.item && val.item.html) {
|
||||
resizeVideo(data, val.item);
|
||||
}
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// calculate real viewport in pixels
|
||||
const vpW = data.viewportSize.x;
|
||||
let vpH = data.viewportSize.y;
|
||||
const ratio = curItem.vw / curItem.vh;
|
||||
let resultW;
|
||||
const $container = $(curItem.container);
|
||||
|
||||
const bars = data.options.barsSize;
|
||||
let barTop = 0;
|
||||
let barBot = 0;
|
||||
if (bars) {
|
||||
barTop = bars.top && bars.top !== 'auto' ? bars.top : 0;
|
||||
barBot = bars.bottom && bars.bottom !== 'auto' ? bars.bottom : 0;
|
||||
}
|
||||
vpH -= barTop + barBot;
|
||||
|
||||
if (ratio > vpW / vpH) {
|
||||
resultW = vpW;
|
||||
} else {
|
||||
resultW = vpH * ratio;
|
||||
}
|
||||
|
||||
const $videoCont = $container.find('.vp-pswp-video');
|
||||
|
||||
$videoCont.css('max-width', resultW);
|
||||
$videoCont.children().css({
|
||||
paddingBottom: `${100 * (curItem.vh / curItem.vw)}%`,
|
||||
});
|
||||
|
||||
$container.css({
|
||||
top: barTop,
|
||||
bottom: barBot,
|
||||
});
|
||||
}
|
||||
|
||||
if (PhotoSwipe && VPPopupAPI) {
|
||||
let pswpInstance;
|
||||
|
||||
// prepare photoswipe markup
|
||||
if (!$('.vp-pswp').length) {
|
||||
const markup = `
|
||||
<div class="pswp vp-pswp${
|
||||
settingsPopupGallery.click_to_zoom ? '' : ' vp-pswp-no-zoom'
|
||||
}" tabindex="-1" role="dialog" aria-hidden="true">
|
||||
<div class="pswp__bg"></div>
|
||||
<div class="pswp__scroll-wrap">
|
||||
<div class="pswp__container">
|
||||
<div class="pswp__item"></div>
|
||||
<div class="pswp__item"></div>
|
||||
<div class="pswp__item"></div>
|
||||
</div>
|
||||
<div class="pswp__ui pswp__ui--hidden">
|
||||
<div class="pswp__top-bar">
|
||||
<div class="pswp__counter"></div>
|
||||
<button class="pswp__button pswp__button--close" title="${
|
||||
__.pswp_close
|
||||
}"></button>
|
||||
<button class="pswp__button pswp__button--share" title="${
|
||||
__.pswp_share
|
||||
}"></button>
|
||||
<button class="pswp__button pswp__button--fs" title="${
|
||||
__.pswp_fs
|
||||
}"></button>
|
||||
<button class="pswp__button pswp__button--zoom" title="${
|
||||
__.pswp_zoom
|
||||
}"></button>
|
||||
</div>
|
||||
<div class="pswp__preloader">
|
||||
<div class="pswp__preloader__icn">
|
||||
<div class="pswp__preloader__cut">
|
||||
<div class="pswp__preloader__donut"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap">
|
||||
<div class="pswp__share-tooltip"></div>
|
||||
</div>
|
||||
<button class="pswp__button pswp__button--arrow--left" title="${
|
||||
__.pswp_prev
|
||||
}"></button>
|
||||
<button class="pswp__button pswp__button--arrow--right" title="${
|
||||
__.pswp_next
|
||||
}"></button>
|
||||
<div class="pswp__caption">
|
||||
<div class="pswp__caption__center"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
$('body').append(markup);
|
||||
}
|
||||
|
||||
// Extend Popup API.
|
||||
VPPopupAPI.vendor = 'photoswipe';
|
||||
VPPopupAPI.open = function (items, index, self) {
|
||||
const finalItems = [];
|
||||
|
||||
// prepare items for fancybox api.
|
||||
items.forEach((item) => {
|
||||
if (item.type === 'embed') {
|
||||
finalItems.push({
|
||||
html: `<div class="vp-pswp-video"><div>${item.embed}</div></div>`,
|
||||
vw: item.width || 0,
|
||||
vh: item.height || 0,
|
||||
title: item.caption,
|
||||
});
|
||||
} else {
|
||||
finalItems.push({
|
||||
src: item.src,
|
||||
el: item.el,
|
||||
w: item.width || 0,
|
||||
h: item.height || 0,
|
||||
title: item.caption,
|
||||
o: {
|
||||
src: item.src,
|
||||
w: item.width || 0,
|
||||
h: item.height || 0,
|
||||
},
|
||||
...(item.srcMedium
|
||||
? {
|
||||
m: {
|
||||
src: item.srcMedium,
|
||||
w: item.srcMediumWidth || 0,
|
||||
h: item.srcMediumHeight || 0,
|
||||
},
|
||||
msrc: item.srcMedium,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const $pswpElement = $('.vp-pswp');
|
||||
const pswpElement = $pswpElement[0];
|
||||
|
||||
// define options (if needed)
|
||||
const options = {
|
||||
captionAndToolbarShowEmptyCaptions: false,
|
||||
closeEl: settingsPopupGallery.show_close_button,
|
||||
captionEl: true,
|
||||
fullscreenEl: settingsPopupGallery.show_fullscreen_button,
|
||||
zoomEl: settingsPopupGallery.show_zoom_button,
|
||||
shareEl: settingsPopupGallery.show_share_button,
|
||||
counterEl: settingsPopupGallery.show_counter,
|
||||
arrowEl: settingsPopupGallery.show_arrows,
|
||||
shareButtons: [
|
||||
{
|
||||
id: 'facebook',
|
||||
label: __.pswp_share_fb,
|
||||
url: 'https://www.facebook.com/sharer/sharer.php?u={{url}}',
|
||||
},
|
||||
{
|
||||
id: 'twitter',
|
||||
label: __.pswp_share_tw,
|
||||
url: 'https://twitter.com/intent/tweet?text={{text}}&url={{url}}',
|
||||
},
|
||||
{
|
||||
id: 'pinterest',
|
||||
label: __.pswp_share_pin,
|
||||
url: 'https://www.pinterest.com/pin/create/button/?url={{url}}&media={{image_url}}&description={{text}}',
|
||||
},
|
||||
],
|
||||
getImageURLForShare() {
|
||||
const currentItem = items[pswpInstance.getCurrentIndex()];
|
||||
|
||||
if (currentItem.type === 'image' && currentItem.src) {
|
||||
return currentItem.src;
|
||||
}
|
||||
|
||||
return pswpInstance.currItem.src || '';
|
||||
},
|
||||
getPageURLForShare() {
|
||||
const currentItem = items[pswpInstance.getCurrentIndex()];
|
||||
|
||||
if (currentItem.type === 'image' && currentItem.src) {
|
||||
return currentItem.src;
|
||||
}
|
||||
|
||||
return window.location.href;
|
||||
},
|
||||
getTextForShare() {
|
||||
const currentItem = items[pswpInstance.getCurrentIndex()];
|
||||
|
||||
if (currentItem.caption) {
|
||||
const $caption = $(currentItem.caption);
|
||||
|
||||
if (
|
||||
$caption.filter('.vp-portfolio__item-popup-title')
|
||||
.length
|
||||
) {
|
||||
return $caption
|
||||
.filter('.vp-portfolio__item-popup-title')
|
||||
.text();
|
||||
}
|
||||
if (
|
||||
$caption.filter('.vp-portfolio__item-popup-description')
|
||||
.length
|
||||
) {
|
||||
return $caption
|
||||
.filter('.vp-portfolio__item-popup-description')
|
||||
.text();
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
bgOpacity: 1,
|
||||
tapToClose: false,
|
||||
tapToToggleControls: true,
|
||||
showHideOpacity: true,
|
||||
history: false,
|
||||
getThumbBoundsFn(thumbIndex) {
|
||||
if (!finalItems[thumbIndex] || !finalItems[thumbIndex].el) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const $el = $(finalItems[thumbIndex].el).find('img')[0];
|
||||
|
||||
if (!$el) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rect = $el.getBoundingClientRect();
|
||||
const pageYScroll =
|
||||
window.pageYOffset || document.documentElement.scrollTop;
|
||||
const pswpTop = parseFloat($pswpElement.css('top')) || 0;
|
||||
|
||||
return {
|
||||
x: rect.left,
|
||||
y: rect.top + pageYScroll - pswpTop,
|
||||
w: rect.width,
|
||||
h: rect.height,
|
||||
};
|
||||
},
|
||||
getDoubleTapZoom(isMouseClick, item) {
|
||||
// isMouseClick - true if mouse, false if double-tap
|
||||
// item - slide object that is zoomed, usually current
|
||||
// item.initialZoomLevel - initial scale ratio of image
|
||||
// e.g. if viewport is 700px and image is 1400px,
|
||||
// initialZoomLevel will be 0.5
|
||||
if (isMouseClick) {
|
||||
// is mouse click on image or zoom icon
|
||||
|
||||
// Click to zoom disabled.
|
||||
if (!settingsPopupGallery.click_to_zoom) {
|
||||
return item.initialZoomLevel;
|
||||
}
|
||||
|
||||
// In case the image is vertically wide, zoom it to fit screen width only.
|
||||
// - check if original image size is wider than screen
|
||||
// - check if zoomed out image in less than 25% of the screen width
|
||||
if (
|
||||
item.w > window.innerWidth &&
|
||||
(item.w * item.initialZoomLevel) / window.innerWidth <
|
||||
0.25
|
||||
) {
|
||||
return window.innerWidth / item.w;
|
||||
}
|
||||
|
||||
// zoom to original
|
||||
return 1;
|
||||
|
||||
// e.g. for 1400px image:
|
||||
// 0.5 - zooms to 700px
|
||||
// 2 - zooms to 2800px
|
||||
}
|
||||
|
||||
// zoom to original if initial zoom is less than 0.7x,
|
||||
// otherwise to 1.5x, to make sure that double-tap gesture always zooms image.
|
||||
return item.initialZoomLevel < 0.7 ? 1 : 1.5;
|
||||
},
|
||||
};
|
||||
|
||||
options.index = parseInt(index, 10);
|
||||
|
||||
// exit if index not found
|
||||
if (!isNumber(options.index)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass data to PhotoSwipe and initialize it
|
||||
pswpInstance = new PhotoSwipe(
|
||||
pswpElement,
|
||||
PhotoSwipeUIDefault,
|
||||
finalItems,
|
||||
options
|
||||
);
|
||||
|
||||
// see: http://photoswipe.com/documentation/responsive-images.html
|
||||
let realViewportWidth;
|
||||
let useLargeImages = false;
|
||||
let firstResize = true;
|
||||
let imageSrcWillChange;
|
||||
|
||||
pswpInstance.listen('beforeResize', () => {
|
||||
// pswpInstance.viewportSize.x - width of PhotoSwipe viewport
|
||||
// pswpInstance.viewportSize.y - height of PhotoSwipe viewport
|
||||
// window.devicePixelRatio - ratio between physical pixels and device independent pixels (Number)
|
||||
// 1 (regular display), 2 (@2x, retina) ...
|
||||
|
||||
// calculate real pixels when size changes
|
||||
realViewportWidth =
|
||||
pswpInstance.viewportSize.x * window.devicePixelRatio;
|
||||
|
||||
// Code below is needed if you want image to switch dynamically on window.resize
|
||||
|
||||
// Find out if current images need to be changed
|
||||
if (useLargeImages && realViewportWidth < 1000) {
|
||||
useLargeImages = false;
|
||||
imageSrcWillChange = true;
|
||||
} else if (!useLargeImages && realViewportWidth >= 1000) {
|
||||
useLargeImages = true;
|
||||
imageSrcWillChange = true;
|
||||
}
|
||||
|
||||
// Invalidate items only when source is changed and when it's not the first update
|
||||
if (imageSrcWillChange && !firstResize) {
|
||||
// invalidateCurrItems sets a flag on slides that are in DOM,
|
||||
// which will force update of content (image) on window.resize.
|
||||
pswpInstance.invalidateCurrItems();
|
||||
}
|
||||
|
||||
if (firstResize) {
|
||||
firstResize = false;
|
||||
}
|
||||
|
||||
imageSrcWillChange = false;
|
||||
});
|
||||
|
||||
pswpInstance.listen('gettingData', (idx, item) => {
|
||||
// Prepare iframes.
|
||||
if (item.html) {
|
||||
// -- Iframe Autoplay - Part 1 --
|
||||
// Disable autoplay parameter in iframes on inactive slides.
|
||||
// Mostly for Youtube and Vimeo to prevent video playing in background.
|
||||
// Later we add autoplay only to active slides.
|
||||
item.html = item.html.replace(/autoplay=1/, 'autoplay=0');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare image sizes.
|
||||
if (useLargeImages && item.o) {
|
||||
if (item.o.src) {
|
||||
item.src = item.o.src;
|
||||
}
|
||||
if (item.o.w) {
|
||||
item.w = item.o.w;
|
||||
}
|
||||
if (item.o.h) {
|
||||
item.h = item.o.h;
|
||||
}
|
||||
} else if (item.m) {
|
||||
if (item.m.src) {
|
||||
item.src = item.m.src;
|
||||
}
|
||||
if (item.m.w) {
|
||||
item.w = item.m.w;
|
||||
}
|
||||
if (item.m.h) {
|
||||
item.h = item.m.h;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pswpInstance.listen('imageLoadComplete', (idx, item) => {
|
||||
if (item.h < 1 || item.w < 1) {
|
||||
const img = new Image();
|
||||
|
||||
img.onload = () => {
|
||||
item.w = img.width;
|
||||
item.h = img.height;
|
||||
pswpInstance.invalidateCurrItems();
|
||||
pswpInstance.updateSize(true);
|
||||
};
|
||||
|
||||
img.src = item.src;
|
||||
}
|
||||
});
|
||||
|
||||
pswpInstance.listen('resize', function () {
|
||||
resizeVideo(this);
|
||||
});
|
||||
|
||||
pswpInstance.listen('afterChange', function () {
|
||||
resizeVideo(this);
|
||||
|
||||
if (self) {
|
||||
self.emitEvent('afterChangePhotoSwipe', [this, pswpInstance]);
|
||||
}
|
||||
});
|
||||
|
||||
// disable video play if no active.
|
||||
pswpInstance.listen('beforeChange', function () {
|
||||
const data = this;
|
||||
|
||||
// -- Iframe Autoplay - Part 2 --
|
||||
// Set autoplay to 1 on active slides and to 0 on inactive.
|
||||
if (data && data.itemHolders.length) {
|
||||
const currentIndex = data.getCurrentIndex();
|
||||
|
||||
data.itemHolders.forEach((val) => {
|
||||
const $iframe = val.el
|
||||
? $(val.el).find('.vp-pswp-video iframe')
|
||||
: false;
|
||||
|
||||
if ($iframe && $iframe.length) {
|
||||
if (val.index === currentIndex) {
|
||||
$iframe.attr(
|
||||
'src',
|
||||
$iframe
|
||||
.attr('src')
|
||||
.replace(/autoplay=0/, 'autoplay=1')
|
||||
);
|
||||
} else {
|
||||
$iframe.attr(
|
||||
'src',
|
||||
$iframe
|
||||
.attr('src')
|
||||
.replace(/autoplay=1/, 'autoplay=0')
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (self) {
|
||||
self.emitEvent('beforeChangePhotoSwipe', [data, pswpInstance]);
|
||||
}
|
||||
});
|
||||
|
||||
// destroy event.
|
||||
pswpInstance.listen('destroy', function () {
|
||||
const data = this;
|
||||
|
||||
if (data) {
|
||||
// Remove video block.
|
||||
if (data.itemHolders.length) {
|
||||
data.itemHolders.forEach((val) => {
|
||||
if (val.el) {
|
||||
$(val.el).find('.vp-pswp-video').remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const currentItemData = items[data.getCurrentIndex()];
|
||||
|
||||
if (currentItemData) {
|
||||
VPPopupAPI.maybeFocusGalleryItem(currentItemData);
|
||||
}
|
||||
|
||||
if (self) {
|
||||
self.emitEvent('beforeClosePhotoSwipe', [
|
||||
options,
|
||||
items,
|
||||
pswpInstance,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
pswpInstance = false;
|
||||
});
|
||||
|
||||
if (self) {
|
||||
self.emitEvent('beforeInitPhotoSwipe', [
|
||||
options,
|
||||
finalItems,
|
||||
index,
|
||||
pswpInstance,
|
||||
]);
|
||||
}
|
||||
|
||||
pswpInstance.init();
|
||||
|
||||
if (self) {
|
||||
self.emitEvent('initPhotoSwipe', [
|
||||
options,
|
||||
finalItems,
|
||||
index,
|
||||
pswpInstance,
|
||||
]);
|
||||
}
|
||||
};
|
||||
VPPopupAPI.close = function () {
|
||||
if (pswpInstance) {
|
||||
pswpInstance.close();
|
||||
pswpInstance = false;
|
||||
}
|
||||
};
|
||||
}
|
431
wp-content/plugins/visual-portfolio/assets/js/plugin-swiper.js
Normal file
431
wp-content/plugins/visual-portfolio/assets/js/plugin-swiper.js
Normal file
@ -0,0 +1,431 @@
|
||||
import isNumber from 'is-number';
|
||||
import $ from 'jquery';
|
||||
|
||||
const $doc = $(document);
|
||||
const { screenSizes } = window.VPData;
|
||||
|
||||
function getSwiperVersion(Swiper) {
|
||||
let ver = 8;
|
||||
|
||||
// in version 8 added new parameter `maxBackfaceHiddenSlides`.
|
||||
if (typeof Swiper.defaults.maxBackfaceHiddenSlides === 'undefined') {
|
||||
ver = 7;
|
||||
}
|
||||
|
||||
// in version 7 added new parameter `rewind`.
|
||||
if (typeof Swiper.defaults.rewind === 'undefined') {
|
||||
ver = 6;
|
||||
}
|
||||
|
||||
// in version 6 added new parameter `loopPreventsSlide`.
|
||||
if (typeof Swiper.defaults.loopPreventsSlide === 'undefined') {
|
||||
ver = 5;
|
||||
}
|
||||
|
||||
return ver;
|
||||
}
|
||||
|
||||
// Extend VP class.
|
||||
$doc.on('extendClass.vpf', (event, VP) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init Swiper plugin
|
||||
*
|
||||
* @param {mixed} options - slider options.
|
||||
*/
|
||||
VP.prototype.initSwiper = function (options = false) {
|
||||
const self = this;
|
||||
|
||||
if (
|
||||
self.options.layout === 'slider' &&
|
||||
typeof window.Swiper !== 'undefined'
|
||||
) {
|
||||
const $parent = self.$items_wrap.parent();
|
||||
|
||||
$parent.addClass('swiper');
|
||||
self.$items_wrap.addClass('swiper-wrapper');
|
||||
self.$items_wrap.children().addClass('swiper-slide');
|
||||
|
||||
// calculate responsive.
|
||||
let slidesPerView = self.options.sliderSlidesPerView || 3;
|
||||
const breakPoints = {};
|
||||
|
||||
if (self.options.sliderEffect === 'fade') {
|
||||
slidesPerView = 1;
|
||||
}
|
||||
|
||||
if (isNumber(slidesPerView)) {
|
||||
let count = slidesPerView;
|
||||
let currentPoint = Math.min(screenSizes.length - 1, count - 1);
|
||||
|
||||
for (; currentPoint >= 0; currentPoint -= 1) {
|
||||
if (
|
||||
count > 0 &&
|
||||
typeof screenSizes[currentPoint] !== 'undefined'
|
||||
) {
|
||||
breakPoints[screenSizes[currentPoint] + 1] = {
|
||||
slidesPerView: count,
|
||||
};
|
||||
}
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
slidesPerView = count || 1;
|
||||
}
|
||||
|
||||
let optionsThumbs = false;
|
||||
let $thumbsParent = false;
|
||||
options = options || {
|
||||
speed: (parseFloat(self.options.sliderSpeed) || 0) * 1000,
|
||||
autoHeight: self.options.sliderItemsHeight === 'auto',
|
||||
effect: self.options.sliderEffect || 'slide',
|
||||
// fix fade items collapse (mostly in Default items style).
|
||||
fadeEffect: {
|
||||
crossFade: true,
|
||||
},
|
||||
spaceBetween: parseFloat(self.options.itemsGap) || 0,
|
||||
centeredSlides: self.options.sliderCenteredSlides === 'true',
|
||||
freeMode: {
|
||||
enabled: self.options.sliderFreeMode === 'true',
|
||||
sticky: self.options.sliderFreeModeSticky === 'true',
|
||||
},
|
||||
loop: self.options.sliderLoop === 'true',
|
||||
// This feature is cool, but not working properly when loop enabled
|
||||
// and fast clicking on previous button is not working properly
|
||||
// https://github.com/nolimits4web/swiper/issues/5945
|
||||
// loopPreventsSlide: false,
|
||||
autoplay: parseFloat(self.options.sliderAutoplay) > 0 && {
|
||||
delay: parseFloat(self.options.sliderAutoplay) * 1000,
|
||||
disableOnInteraction: false,
|
||||
},
|
||||
navigation: self.options.sliderArrows === 'true' && {
|
||||
nextEl: '.vp-portfolio__items-arrow-next',
|
||||
prevEl: '.vp-portfolio__items-arrow-prev',
|
||||
},
|
||||
pagination: self.options.sliderBullets === 'true' && {
|
||||
el: '.vp-portfolio__items-bullets',
|
||||
clickable: true,
|
||||
dynamicBullets:
|
||||
self.options.sliderBulletsDynamic === 'true',
|
||||
renderBullet(index, className) {
|
||||
return `<span class="${className}" data-bullet-index="${index}" data-bullet-number="${
|
||||
index + 1
|
||||
}"></span>`;
|
||||
},
|
||||
},
|
||||
mousewheel: self.options.sliderMousewheel === 'true',
|
||||
slidesPerView,
|
||||
breakpoints: breakPoints,
|
||||
keyboard: true,
|
||||
grabCursor: true,
|
||||
preloadImages: false,
|
||||
|
||||
// fixes text selection when swipe in the items gap.
|
||||
touchEventsTarget: 'container',
|
||||
};
|
||||
|
||||
// fix first load slide position (seems like a conflict with lazySizes)
|
||||
// issue: https://github.com/nk-crew/visual-portfolio/issues/54
|
||||
if (options.speed === 0) {
|
||||
options.speed = 1;
|
||||
}
|
||||
let positionFix = 0;
|
||||
|
||||
options.on = {
|
||||
transitionEnd() {
|
||||
if (positionFix === 0) {
|
||||
positionFix = 1;
|
||||
this.setTransition(1);
|
||||
this.setTranslate(this.translate + 0.1);
|
||||
} else if (positionFix === 1) {
|
||||
positionFix = 2;
|
||||
this.slideReset();
|
||||
}
|
||||
},
|
||||
// These events used to add fixes for
|
||||
// conflict with custom cursor movement.
|
||||
touchStart(swiper, e) {
|
||||
self.emitEvent('swiperTouchStart', [swiper, e]);
|
||||
},
|
||||
touchMove(swiper, e) {
|
||||
self.emitEvent('swiperTouchMove', [swiper, e]);
|
||||
},
|
||||
touchEnd(swiper, e) {
|
||||
self.emitEvent('swiperTouchEnd', [swiper, e]);
|
||||
},
|
||||
};
|
||||
|
||||
self.emitEvent('beforeInitSwiper', [options]);
|
||||
|
||||
// thumbnails.
|
||||
if (self.$slider_thumbnails_wrap.length) {
|
||||
$thumbsParent = self.$slider_thumbnails_wrap.parent();
|
||||
|
||||
$thumbsParent.addClass('swiper');
|
||||
self.$slider_thumbnails_wrap.addClass('swiper-wrapper');
|
||||
self.$slider_thumbnails_wrap
|
||||
.children()
|
||||
.addClass('swiper-slide');
|
||||
|
||||
// calculate responsive.
|
||||
let thumbnailsPerView =
|
||||
self.options.sliderThumbnailsPerView || 8;
|
||||
const thumbnailsBreakPoints = {};
|
||||
|
||||
if (isNumber(thumbnailsPerView)) {
|
||||
let count = thumbnailsPerView;
|
||||
let currentPoint = Math.min(
|
||||
screenSizes.length - 1,
|
||||
count - 1
|
||||
);
|
||||
|
||||
for (; currentPoint >= 0; currentPoint -= 1) {
|
||||
if (
|
||||
count > 0 &&
|
||||
typeof screenSizes[currentPoint] !== 'undefined'
|
||||
) {
|
||||
thumbnailsBreakPoints[
|
||||
screenSizes[currentPoint] + 1
|
||||
] = {
|
||||
slidesPerView: count,
|
||||
};
|
||||
}
|
||||
count -= 1;
|
||||
}
|
||||
|
||||
thumbnailsPerView = count || 1;
|
||||
}
|
||||
|
||||
optionsThumbs = {
|
||||
autoHeight: self.options.sliderThumbnailsHeight === 'auto',
|
||||
effect: 'slide',
|
||||
spaceBetween:
|
||||
parseFloat(self.options.sliderThumbnailsGap) || 0,
|
||||
loop: false,
|
||||
// This feature is cool, but not working properly when loop enabled
|
||||
// and fast clicking on previous button is not working properly
|
||||
// https://github.com/nolimits4web/swiper/issues/5945
|
||||
// loopPreventsSlide: false,
|
||||
freeMode: {
|
||||
enabled: true,
|
||||
sticky: true,
|
||||
},
|
||||
loopedSlides: 5,
|
||||
slidesPerView: thumbnailsPerView,
|
||||
breakpoints: thumbnailsBreakPoints,
|
||||
keyboard: true,
|
||||
grabCursor: true,
|
||||
watchSlidesVisibility: true,
|
||||
watchSlidesProgress: true,
|
||||
preloadImages: false,
|
||||
|
||||
// fixed text selection when swipe in the items gap.
|
||||
touchEventsTarget: 'container',
|
||||
on: {
|
||||
// These events used to add fixes for
|
||||
// conflict with custom cursor movement.
|
||||
touchStart(swiper, e) {
|
||||
self.emitEvent('swiperTouchStart', [swiper, e]);
|
||||
},
|
||||
touchMove(swiper, e) {
|
||||
self.emitEvent('swiperTouchMove', [swiper, e]);
|
||||
},
|
||||
touchEnd(swiper, e) {
|
||||
self.emitEvent('swiperTouchEnd', [swiper, e]);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Fallbacks for old Swiper versions.
|
||||
(() => {
|
||||
const swiperVersion = getSwiperVersion(window.Swiper);
|
||||
const isThumbsEnabled =
|
||||
optionsThumbs && $thumbsParent && $thumbsParent[0];
|
||||
|
||||
// Since v7 used container class `swiper`, we should also add old `swiper-container` class.
|
||||
if (swiperVersion < 7) {
|
||||
$parent.addClass('swiper-container');
|
||||
|
||||
if (isThumbsEnabled) {
|
||||
$thumbsParent.addClass('swiper-container');
|
||||
}
|
||||
}
|
||||
|
||||
// Since v7 freeMode options moved under `freeMode` object.
|
||||
if (swiperVersion < 7) {
|
||||
options.freeModeSticky = options.freeMode.sticky;
|
||||
options.freeMode = options.freeMode.enabled;
|
||||
|
||||
if (isThumbsEnabled) {
|
||||
optionsThumbs.freeModeSticky =
|
||||
optionsThumbs.freeMode.sticky;
|
||||
optionsThumbs.freeMode = optionsThumbs.freeMode.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
// Since v5 `breakpointsInverse` option is removed and it is now `true` by default, but in older versions it was `false`.
|
||||
if (swiperVersion >= 5) {
|
||||
options.breakpointsInverse = true;
|
||||
|
||||
if (isThumbsEnabled) {
|
||||
optionsThumbs.breakpointsInverse = true;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// Init Swiper.
|
||||
if (optionsThumbs && $thumbsParent && $thumbsParent[0]) {
|
||||
const swiperThumbs = new window.Swiper(
|
||||
$thumbsParent[0],
|
||||
optionsThumbs
|
||||
);
|
||||
|
||||
options.thumbs = {
|
||||
swiper: swiperThumbs,
|
||||
};
|
||||
}
|
||||
const instance = new window.Swiper($parent[0], options);
|
||||
|
||||
// Autoplay Hover Pause.
|
||||
if (
|
||||
self.options.sliderAutoplayHoverPause === 'true' &&
|
||||
parseFloat(self.options.sliderAutoplay) > 0
|
||||
) {
|
||||
self.$item.on(
|
||||
`mouseenter.vpf-uid-${self.uid}`,
|
||||
'.swiper',
|
||||
() => {
|
||||
$parent[0].swiper.autoplay.stop();
|
||||
}
|
||||
);
|
||||
self.$item.on(
|
||||
`mouseleave.vpf-uid-${self.uid}`,
|
||||
'.swiper',
|
||||
() => {
|
||||
$parent[0].swiper.autoplay.start();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
self.emitEvent('initSwiper', [options, instance]);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy Swiper plugin
|
||||
*/
|
||||
VP.prototype.destroySwiper = function () {
|
||||
const self = this;
|
||||
const $parent = self.$items_wrap.parent();
|
||||
const $thumbsParent = self.$slider_thumbnails_wrap.length
|
||||
? self.$slider_thumbnails_wrap.parent()
|
||||
: false;
|
||||
|
||||
const SliderSwiper = $parent[0].swiper;
|
||||
const ThumbsSwiper = $thumbsParent ? $thumbsParent[0].swiper : false;
|
||||
|
||||
let isDestroyed = false;
|
||||
|
||||
// Thumbnails.
|
||||
if (ThumbsSwiper) {
|
||||
ThumbsSwiper.destroy();
|
||||
|
||||
$thumbsParent.removeClass('swiper');
|
||||
self.$slider_thumbnails_wrap.removeClass('swiper-wrapper');
|
||||
self.$slider_thumbnails_wrap.children().removeClass('swiper-slide');
|
||||
|
||||
isDestroyed = true;
|
||||
}
|
||||
|
||||
// Slider.
|
||||
if (SliderSwiper) {
|
||||
SliderSwiper.destroy();
|
||||
|
||||
$parent.removeClass('swiper');
|
||||
self.$items_wrap.removeClass('swiper-wrapper');
|
||||
self.$items_wrap.children().removeClass('swiper-slide');
|
||||
|
||||
$parent
|
||||
.find('.vp-portfolio__items-bullets')
|
||||
.removeClass(
|
||||
'swiper-pagination-clickable swiper-pagination-bullets-dynamic'
|
||||
)
|
||||
.removeAttr('style')
|
||||
.html('');
|
||||
|
||||
isDestroyed = true;
|
||||
}
|
||||
|
||||
if (isDestroyed) {
|
||||
self.emitEvent('destroySwiper');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Add Items.
|
||||
$doc.on('addItems.vpf', (event, self, $items, removeExisting, $newVP) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
const Swiper = self.$items_wrap.parent()[0].swiper;
|
||||
|
||||
if (!Swiper) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Slider.
|
||||
{
|
||||
if (removeExisting) {
|
||||
Swiper.removeAllSlides();
|
||||
}
|
||||
|
||||
const appendArr = [];
|
||||
$items.addClass('swiper-slide').each(function () {
|
||||
appendArr.push(this);
|
||||
});
|
||||
Swiper.appendSlide(appendArr);
|
||||
}
|
||||
|
||||
// Thumbnails.
|
||||
const ThumbsSwiper = self.$slider_thumbnails_wrap.length
|
||||
? self.$slider_thumbnails_wrap.parent()[0].swiper
|
||||
: false;
|
||||
if (ThumbsSwiper) {
|
||||
if (removeExisting) {
|
||||
ThumbsSwiper.removeAllSlides();
|
||||
}
|
||||
|
||||
const appendArr = [];
|
||||
$newVP
|
||||
.find('.vp-portfolio__thumbnails > .vp-portfolio__thumbnail-wrap')
|
||||
.clone()
|
||||
.addClass('swiper-slide')
|
||||
.each(function () {
|
||||
appendArr.push(this);
|
||||
});
|
||||
ThumbsSwiper.appendSlide(appendArr);
|
||||
}
|
||||
});
|
||||
|
||||
// Init.
|
||||
$doc.on('init.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.initSwiper();
|
||||
});
|
||||
|
||||
// Destroy.
|
||||
$doc.on('destroy.vpf', (event, self) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
self.destroySwiper();
|
||||
});
|
635
wp-content/plugins/visual-portfolio/assets/js/popup-gallery.js
Normal file
635
wp-content/plugins/visual-portfolio/assets/js/popup-gallery.js
Normal file
@ -0,0 +1,635 @@
|
||||
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);
|
||||
}
|
||||
);
|
||||
}
|
93
wp-content/plugins/visual-portfolio/assets/js/preview.js
Normal file
93
wp-content/plugins/visual-portfolio/assets/js/preview.js
Normal file
@ -0,0 +1,93 @@
|
||||
import $ from 'jquery';
|
||||
|
||||
const $body = $('body');
|
||||
const $doc = $(document);
|
||||
const $preview = $('#vp_preview');
|
||||
|
||||
// prevent click on links.
|
||||
document.addEventListener(
|
||||
'click',
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
if (window.parentIFrame) {
|
||||
window.parentIFrame.sendMessage('clicked');
|
||||
}
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
// prevent click on <select> and similar elements.
|
||||
document.addEventListener(
|
||||
'mousedown',
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
e.target.blur();
|
||||
window.focus();
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
// add dynamic data to AJAX calls.
|
||||
$doc.on('startLoadingNewItems.vpf', (event, vpObject, url, ajaxData) => {
|
||||
if (event.namespace !== 'vpf') {
|
||||
return;
|
||||
}
|
||||
|
||||
ajaxData.data = Object.assign(
|
||||
ajaxData.data || {},
|
||||
window.vp_preview_post_data
|
||||
);
|
||||
});
|
||||
|
||||
// Dynamic CSS cache.
|
||||
const dynamicCSScache = {};
|
||||
|
||||
// configure iFrame resizer script.
|
||||
window.iFrameResizer = {
|
||||
log: false,
|
||||
heightCalculationMethod() {
|
||||
return $preview.outerHeight(true);
|
||||
},
|
||||
onMessage(data) {
|
||||
if (!data || !data.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data.name) {
|
||||
case 'resize':
|
||||
// This random number needed for proper resize Isotope and other plugins.
|
||||
$body.css('max-width', data.width + Math.random());
|
||||
break;
|
||||
case 'dynamic-css': {
|
||||
// Insert dynamic styles.
|
||||
const styleId = `vp-dynamic-styles-${data.blockId}-inline-css`;
|
||||
|
||||
// Skip if styles haven't changed.
|
||||
if (
|
||||
dynamicCSScache[styleId] &&
|
||||
data.styles === dynamicCSScache[styleId]
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
let $style = $(`#${styleId}`);
|
||||
|
||||
if (!$style.length) {
|
||||
$style = $(`<style id="${styleId}"></style>`).appendTo(
|
||||
'head'
|
||||
);
|
||||
}
|
||||
|
||||
dynamicCSScache[styleId] = data.styles;
|
||||
|
||||
$style.text(data.styles);
|
||||
break;
|
||||
}
|
||||
// no default
|
||||
}
|
||||
},
|
||||
};
|
Reference in New Issue
Block a user