document.addEventListener('DOMContentLoaded', function () {
// Social link copy function.
const socialLinkCopy = () => {
const socialLinks = document.querySelectorAll(
'.sp-smart-post-social-share .sp-smart-post-copy-btn'
);
if (0 < socialLinks.length) {
socialLinks.forEach((element) => {
element.addEventListener('click', (e) => {
e.preventDefault();
const link = e.target
.closest('.sp-smart-post-copy-btn')
.getAttribute('data-url');
if (link && navigator.clipboard) {
navigator.clipboard.writeText(link);
} else {
console.log(
'Clipboard API not supported or link missing.'
);
}
});
});
}
};
// Post Modal custom lightbox.
const modal = () => {
const modalContainers = document.querySelectorAll(
".sp-smart-post-modal-container"
);
let imageSize;
// Utility: Get next or previous post ID based on current
const getNextPostId = (direction, currentId, allIds) => {
const index = allIds.indexOf(String(currentId));
if (index === -1) {
return currentId;
}
if (direction === "next") {
return allIds[(index + 1) % allIds.length];
} else if (direction === "prev") {
return allIds[(index - 1 + allIds.length) % allIds.length];
}
return currentId;
};
const getReadingTime = (content, attr) => {
const text = content.trim();
const characterCount = text.length;
const wordCount = text.split(/\s+/).filter(Boolean).length;
const allCount = {
words: wordCount,
chars: characterCount,
};
attr = JSON.parse(attr);
const type = attr?.unit;
return `${Math.floor(
parseInt(allCount[type]) / parseInt(attr.value)
)} Min Read`;
};
const sendPostIdAjax = (postId, modal) => {
fetch(sp_smart_post_block_localize.ajaxUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
action: "sp_handle_post_id",
nonce: sp_smart_post_block_localize.ajaxNonce,
post_id: postId,
image_size: imageSize,
}),
})
.then((res) => res.json())
.then((data) => {
if (data.success) {
const postData = data.data;
const modalImage = postData.post_image;
const modalTitle = postData.title;
const modalAuthor = postData.author_name;
const modalDate = postData.date_time;
const modalTaxonomy = postData.meta_taxonomy;
const modalBadges = postData.post_badges_list;
const modalContent = postData.content.replace(
/^"|"$/g,
""
);
const modalComment = postData.post_data.comment_count;
const modalViews = postData.post_views;
const modalLike = postData.like_button;
const readingTimeContainer = modal.querySelector(
".sp-smart-post-reading-time"
);
const readValue = readingTimeContainer
? readingTimeContainer.dataset.readTime
: "";
let readTime;
if (readValue) {
readTime = getReadingTime(
modalContent.replace(/<\/?p>/g, ""),
readValue
);
}
modal.querySelector(
".sp-smart-post-card-modal-image"
).innerHTML = modalImage;
modal.querySelector(
".sp-post-modal-title"
).textContent = modalTitle;
const authorEl = modal.querySelector(".sp-meta-author");
if (authorEl) {
authorEl.innerText = modalAuthor;
}
const dateEl = modal.querySelector(".sp-meta-date");
if (dateEl) {
dateEl.innerText = modalDate;
}
const commentsEl =
modal.querySelector(".sp-meta-comments");
if (commentsEl) {
commentsEl.innerText = modalComment;
}
const viewsEl = modal.querySelector(".sp-meta-views");
if (viewsEl) {
viewsEl.innerText = modalViews;
}
const likesEl = modal.querySelector(
".sp-smart-post-likes"
);
if (likesEl) {
likesEl.innerHTML = modalLike;
}
const readTimeEl = modal.querySelector(
".sp-meta-reading-time"
);
if (readTimeEl) {
readTimeEl.innerHTML = readTime;
}
const postBadgesEl = modal.querySelector(
".sp-title-badges-list"
);
if ( postBadgesEl && modalBadges && modalBadges?.length > 0 ) {
const listItems = modalBadges.map( item => `
${item.name}`)?.join("");
postBadgesEl.innerHTML = listItems;
}
modal.querySelector(
".sp-post-modal-excerpt"
).innerHTML = modalContent;
modal.querySelector(
".sp-meta-taxonomy-category"
).innerText = modalTaxonomy;
setTimeout(() => {
modal.style.opacity = 1;
}, 300);
} else {
console.log("PHP error:", data.data);
}
})
.catch((err) => {
console.log("Fetch error:", err);
});
};
const cleanData = (modal) => {
modal.querySelector(".sp-smart-post-card-modal-image").innerHTML =
"";
modal.querySelector(".sp-post-modal-title").textContent = "";
modal.querySelector(".sp-meta-taxonomy-category").innerText = "";
modal.querySelector(".sp-post-modal-excerpt").innerHTML = "";
};
// Open modal and setup navigation
const openModal = (e, container) => {
e.preventDefault();
let modalData;
const modal = container.querySelector( ".sp-smart-post-modal-container" );
imageSize = container.querySelector(
".sp-smart-post-modal-container .sp-smart-post-card-modal-image"
).dataset.imageSize;
const clickedCard = e.target.closest(".sp-smart-post-card");
const clickedTitle = clickedCard?.querySelector(
".sp-smart-post-title-wrapper"
);
if (!clickedTitle) {
return;
}
let activePostId = String(clickedTitle.dataset.postId); // Initialize properly
const allTitles = container.querySelectorAll(
".sp-smart-post-title-wrapper"
);
const allIds = [
...new Set(
Array.from(allTitles).map((item) => item.dataset.postId)
),
];
const nextBtn = container.querySelector(".sp-smart-modal-next");
const prevBtn = container.querySelector(".sp-smart-modal-prev");
// sendPostIdAjax( clickedTitle.dataset.postId, modal );
// Setup navigation button handlers
const setupNav = (btn, direction) => {
if (!btn) {
return;
}
btn.onclick = () => {
activePostId = getNextPostId(
direction,
activePostId,
allIds
);
// modal.style.opacity = 0;
sendPostIdAjax(activePostId, modal);
};
};
setupNav(nextBtn, "next");
setupNav(prevBtn, "prev");
// modal.style.display = "block";
modal.classList.add("is_open");
};
// Close modal
const closeModal = (closeBtn) => {
const modal = closeBtn.closest(".sp-smart-post-modal-container");
// if ( modal ) modal.style.display = "none";
modal.style.opacity = 0;
if (modal) {
modal.classList.remove("is_open");
}
cleanData(modal);
};
// Attach modal close events
modalContainers.forEach((modal) => {
const box = modal.querySelector(".sp-smart-post-modal-content");
const nextBtn = modal.querySelector(".sp-smart-modal-next");
const prevBtn = modal.querySelector(".sp-smart-modal-prev");
modal
.querySelectorAll(".sp-modal-close-btn.cursor")
.forEach((btn) => {
btn.addEventListener("click", () => closeModal(btn));
});
modal.addEventListener("click", (event) => {
const clickedEl = event.target;
// If click is NOT inside box AND not on next/prev buttons
if (
!box.contains(clickedEl) &&
clickedEl !== nextBtn &&
clickedEl !== prevBtn &&
(!nextBtn || !nextBtn.contains(clickedEl)) &&
(!prevBtn || !prevBtn.contains(clickedEl))
) {
closeModal(box);
}
});
});
// Attach open modal triggers using event delegation for better performance
modalContainers.forEach((modal) => {
if (!modal.textContent.trim()) {
return;
}
const container = modal.parentElement;
container.addEventListener("click", (e) => {
// Do not trigger if the click is inside an already open modal.
if (e.target.closest(".sp-smart-post-modal-container")) {
return;
}
const trigger = e.target.closest(
".sp-smart-post-card-image a, .sp-smart-post-title-wrapper, .sp-smart-post-read-more-button a"
);
if (!trigger) {
return;
}
const clickedCard = trigger.closest(".sp-smart-post-card");
if (!clickedCard) {
return;
}
const clickedTitle = clickedCard.querySelector(
".sp-smart-post-title-wrapper"
);
if (!clickedTitle || !clickedTitle.dataset.postId) {
return;
}
e.preventDefault();
modal.style.opacity = 0;
sendPostIdAjax(clickedTitle.dataset.postId, modal);
openModal(e, container);
});
});
};
// Initialize image gallery and video players.
const initSmartPostFeatures = (context = document) => {
// --- Slider Init ---
const sliderWrappers = context.querySelectorAll(".sp-slider-wrapper");
sliderWrappers.forEach(slideWrapper => {
if (!slideWrapper.classList.contains("sp-slider-initialized")) {
new SPSlider(slideWrapper, {
loop: true,
autoplay: true,
interval: 3000,
speed: 600,
});
slideWrapper.classList.add("sp-slider-initialized");
}
});
// --- Video Player Init ---
const videoPostList = context.querySelectorAll(".sp-smart-video-player");
videoPostList.forEach(videoContainer => {
if (!videoContainer.classList.contains("sp-video-initialized")) {
const videoUrl = videoContainer.dataset.video_url;
const featureImage = videoContainer.dataset.image_url;
const videoCaption = videoContainer.dataset.video_caption;
new SpSmartVideoPlayer(videoContainer, videoUrl, {
featureImage,
videoCaption,
showFeatureImage: true,
transitionSpeed: 500,
});
videoContainer.classList.add("sp-video-initialized");
}
videoContainer.addEventListener("click", function ( event ) {
event.preventDefault();
event.stopPropagation();
const featureVideo = videoContainer.querySelector("video");
if ( featureVideo ) {
if (featureVideo.paused) {
featureVideo.play();
} else {
featureVideo.pause();
}
}
});
});
}
initSmartPostFeatures();
socialLinkCopy();
modal();
/**
* Carousel Pagination Function.
* @param paginationClass
*/
const paginationDotNumber = (paginationClass) => {
return {
el: paginationClass,
clickable: true,
renderBullet(index, className) {
return (
'' + (index + 1) + ''
);
},
};
};
/**
* Helper to safely parse JSON.
* @param str
* @param fallback
*/
const safeJSONParse = (str, fallback = {}) => {
try {
return JSON.parse(str) || fallback;
} catch (e) {
console.log('Failed to parse JSON:', str, e);
return fallback;
}
};
/**
* Initializes Timeline layout with dynamic height adjustment.
*/
document.querySelectorAll('.post-timeline-three').forEach((containerEl) => {
const swiperEl = containerEl.querySelector('.swiper-container');
if (!swiperEl) {
return;
}
const postContainers = containerEl.querySelectorAll(
'.sp-smart-post-timeline-three-post-container'
);
const wrapper = document.querySelector(
'.timeline-three-layout-two .swiper-wrapper'
);
const timelineBorder = document.querySelector(
'.timeline-three-layout-two .sp-smart-post-timeline-border'
);
// Calculate max height from posts.
let maxHeight = 0;
postContainers.forEach((post) => {
maxHeight = Math.max(maxHeight, post.clientHeight);
});
maxHeight += 60; // Add margin
// Apply calculated height
if (wrapper) {
wrapper.style.marginTop = `${maxHeight}px`;
}
if (timelineBorder) {
timelineBorder.style.marginTop = `${maxHeight}px`;
}
const options = safeJSONParse(
swiperEl.getAttribute('data-swiper-options')
);
if (!Object.keys(options).length) {
return;
}
new PCPSwiper(swiperEl, options);
});
/**
* Initializes thumbnail sliders (layout one).
*/
document
.querySelectorAll(
'.sp-smart-post-thumbnail-slider.sp-smart-post-block-wrapper'
)
.forEach((containerEl) => {
const [mainSwiperEl, thumbsSwiperEl] =
containerEl.querySelectorAll('.swiper-container');
const thumbsOptions = safeJSONParse(
containerEl
.querySelector('.sp-smart-post-swiper2')
?.getAttribute('data-swiper-options')
);
const mainOptions = safeJSONParse(
mainSwiperEl?.getAttribute('data-swiper-options')
);
if (
!mainSwiperEl ||
!thumbsSwiperEl ||
!Object.keys(mainOptions).length
) {
return;
}
const thumbsSwiper = new PCPSwiper(thumbsSwiperEl, thumbsOptions);
const carouselPagination = mainOptions?.pagination;
const paginationClassName = carouselPagination?.el;
const numberPagination = mainOptions?.pagination?.paginationType
? true
: false;
const mainSwiper = new PCPSwiper(mainSwiperEl, {
...mainOptions,
thumbs: { swiper: thumbsSwiper },
pagination: !numberPagination
? carouselPagination
: paginationDotNumber(paginationClassName),
});
});
/**
* Initializes thumbnail sliders (layout two).
*/
document
.querySelectorAll(
'.sp-smart-post-block-wrapper.sp-smart-thumbnail-slider-two'
)
.forEach((containerEl) => {
const [mainSwiperEl, thumbsSwiperEl] =
containerEl.querySelectorAll('.swiper-container');
const thumbsOptions = safeJSONParse(
containerEl
.querySelector('.sp-smart-post-swiper2')
?.getAttribute('data-swiper-options')
);
const mainOptions = safeJSONParse(
mainSwiperEl?.getAttribute('data-swiper-options')
);
if (
!mainSwiperEl ||
!thumbsSwiperEl ||
!Object.keys(mainOptions).length
) {
return;
}
const thumbsSwiper = new PCPSwiper(thumbsSwiperEl, thumbsOptions);
const mainSwiper = new PCPSwiper(mainSwiperEl, {
...mainOptions,
thumbs: { swiper: thumbsSwiper },
});
});
/**
* Throttles a function using requestAnimationFrame for performance.
* @param {Function} callback The function to throttle.
* @return {Function} The throttled function.
*/
const throttle = (callback) => {
let ticking = false;
return (...args) => {
if (!ticking) {
window.requestAnimationFrame(() => {
callback(...args);
ticking = false;
});
ticking = true;
}
};
};
/**
* Initializes and manages scroll-based animations for all timeline blocks on the page.
* Caches DOM elements and uses a single throttled scroll listener for better performance.
*/
const initTimelineScroll = () => {
const allTimelinesData = [];
document
.querySelectorAll('.sp-smart-post-show-scroll-wrapper')
.forEach((timelineWrapper) => {
const blockId = timelineWrapper.id;
if (!blockId) {
return;
}
const postCards = timelineWrapper.querySelectorAll(
'.sp-smart-post-timeline-one-post-container .sp-smart-indicator-circle'
);
const indicatorArrows = timelineWrapper.querySelectorAll(
'.sp-smart-post-timeline-one-post-container .sp-smart-indicator-arrow'
);
const timelineContainer = timelineWrapper.querySelector(
'.sp-smart-post-timeline-container'
);
// Pre-create or find the style tag for this instance to avoid creating it on every scroll.
let styleTag = timelineWrapper.querySelector(
'style.sps-dynamic-styles'
);
if (!styleTag) {
styleTag = document.createElement('style');
styleTag.className = 'sps-dynamic-styles';
timelineWrapper.insertBefore(
styleTag,
timelineWrapper.firstChild
);
}
if (timelineContainer) {
allTimelinesData.push({
wrapper: timelineWrapper,
blockId,
postCards,
indicatorArrows,
timelineContainer,
styleTag,
});
}
});
// 2. Skip if no timelines are found
if (allTimelinesData.length === 0) {
return;
}
// 3. Create a single, efficient scroll handler for all timelines
const handleTimelineScrolls = () => {
const scrollY = window.scrollY;
const pageHeight = Math.max(document.body.offsetHeight);
allTimelinesData.forEach((data) => {
const wrapperRect = data.wrapper.getBoundingClientRect();
// Only process timelines currently visible in the viewport (with a buffer)
if (
wrapperRect.bottom >= 0 &&
wrapperRect.top <= window.innerHeight
) {
const scrollPosition = scrollY + 200;
// Indicator activation logic
const indicatorActive = (elements) => {
elements.forEach((el) => {
const elTop =
el.getBoundingClientRect().top + scrollY;
if (
scrollY + 505 > elTop ||
scrollPosition + 700 >= pageHeight
) {
el.classList.add('active');
} else {
el.classList.remove('active');
}
});
};
indicatorActive(data.postCards);
indicatorActive(data.indicatorArrows);
// Timeline line height calculation logic
const timelineRect =
data.timelineContainer.getBoundingClientRect();
const timelineTop = timelineRect.top + scrollY;
let height = 320;
let heightUnit = 'px';
if (
timelineRect.bottom - 518 < 0 ||
scrollPosition + 700 >= pageHeight
) {
height = 100;
heightUnit = '%';
} else if (
scrollPosition > timelineTop &&
timelineRect.bottom - 518 > 0
) {
height = scrollPosition + 320 - timelineTop;
}
// Update the dedicated style tag's content, which is more performant than creating new tags.
const styleContent = `#${data.blockId} .sp-smart-post-timeline-container::after { height: ${height}${heightUnit} !important; }`;
if (data.styleTag.textContent !== styleContent) {
data.styleTag.textContent = styleContent;
}
}
});
};
// 4. Add the single throttled listener
window.addEventListener('scroll', throttle(handleTimelineScrolls));
};
// Initialize the timeline scroll animations.
initTimelineScroll();
function updatePaginationData(section, data) {
if (!section || !data) {
return;
}
// Update dataset attributes.
section.dataset.pages = data.total_pages;
section.dataset.current = data.current_page;
if (section?.dataset.pages < 2) {
section.style.display = 'none';
} else {
section.style.display = 'flex';
}
if (section?.dataset.pages < 2) {
return false;
}
updatePaginationUI(section);
createPaginationSection(section);
}
const createPaginationSection = (section) => {
const totalPages = parseInt(section.dataset.pages, 10);
const currentPage = parseInt(section.dataset.current, 10);
const paginationtype = section?.dataset.paginationtype;
if (paginationtype == 'pagination') {
if (!totalPages || totalPages < 2) {
return;
}
const paginationContainer = section.querySelector(
'.sp-smart-post-pagination-buttons'
);
if (!paginationContainer) {
return;
}
paginationContainer.innerHTML = ''; // Clear existing buttons
// Previous Button
const prevBtn = document.createElement('a');
prevBtn.href = '#';
prevBtn.className = 'page-numbers prev';
if (currentPage === 1) {
prevBtn.classList.add('disabled');
}
prevBtn.innerHTML = ` Previous`;
paginationContainer.appendChild(prevBtn);
// Number Buttons
for (let i = 1; i <= totalPages; i++) {
const pageBtn = document.createElement('a');
pageBtn.href = '#';
pageBtn.className = 'page-numbers';
pageBtn.dataset.page = i;
pageBtn.textContent = i;
if (i === currentPage) {
pageBtn.classList.add('current');
}
paginationContainer.appendChild(pageBtn);
}
// Next Button
const nextBtn = document.createElement('a');
nextBtn.href = '#';
nextBtn.className = 'page-numbers next';
if (currentPage === totalPages) {
nextBtn.classList.add('disabled');
}
nextBtn.innerHTML = `Next `;
paginationContainer.appendChild(nextBtn);
}
};
const updatePaginationUI = (section) => {
const current = parseInt(section.dataset.current, 10);
const total = parseInt(section.dataset.pages, 10);
const paginationtype = section?.dataset.paginationtype;
section.querySelectorAll('.page-numbers[data-page]').forEach((link) => {
const page = parseInt(link.dataset.page, 10);
link.classList.toggle('current', page === current);
});
const prev = section.querySelector('.page-numbers.prev');
const next = section.querySelector('.page-numbers.next');
const loadMoreWrapper = section.querySelector(
'.sp-smart-post-load-more-button'
);
if (prev) {
prev.classList.toggle('disabled', current <= 1);
}
if (next) {
next.classList.toggle('disabled', current >= total);
}
// For nav-arrow buttons.
const [prevNav, nextNav] = section.querySelectorAll(
'.sp-smart-post-grid-nav-arrow-btn'
);
if (prevNav && nextNav) {
prevNav.style.pointerEvents = current <= 1 ? 'none' : 'auto';
prevNav.style.opacity = current <= 1 ? '0.5' : '1';
nextNav.style.pointerEvents = current >= total ? 'none' : 'auto';
nextNav.style.opacity = current >= total ? '0.5' : '1';
}
if (loadMoreWrapper) {
if (current >= total) {
const loadMoreWrapperBtn = loadMoreWrapper.querySelector('a');
if (loadMoreWrapperBtn) {
loadMoreWrapperBtn.style.display = 'none';
}
} else {
loadMoreWrapper.style.display = 'block';
}
}
};
function hasFilterCounts(count_data) {
if (!count_data || typeof count_data !== 'object') {
return false;
}
return Object.entries(count_data).some(([key, value]) => {
// Skip pagination
if (key === 'pagination') {
return false;
}
// Check if it's a non-empty array
return Array.isArray(value) && value.length > 0;
});
}
function getBlockParams() {
const url = new URL(window.location.href);
const params = new URLSearchParams(url.search);
const blockParams = {};
for (const [key, value] of params.entries()) {
if (
key === 'block' ||
key.startsWith('tx_') ||
key.startsWith('sps_')
) {
blockParams[key] = value;
}
}
return blockParams;
}
function updateSelectByParams(params, blockParent) {
// Loop through each param and try to update the matching