PluginProbe
Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News / 4.0.3
Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News v4.0.3
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 4.0.2 4.0.1 2.3.5 2.3.6 2.4.0 2.4.1 2.4.10 2.4.11 2.4.12 2.4.13 2.4.14 2.4.15 2.4.16 2.4.17 2.4.18 2.4.19 2.4.2 2.4.20 2.4.21 All 88 releases
post-carousel / blocks / assets / js / script.js

script.js in Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News 4.0.3, at blocks/assets/js/script.js

2,668 lines 75.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 document.addEventListener('DOMContentLoaded', function () {
2 // Social link copy function.
3 const socialLinkCopy = () => {
4 const socialLinks = document.querySelectorAll(
5 '.sp-smart-post-social-share .sp-smart-post-copy-btn'
6 );
7
8 if (0 < socialLinks.length) {
9 socialLinks.forEach((element) => {
10 element.addEventListener('click', (e) => {
11 e.preventDefault();
12 const link = e.target
13 .closest('.sp-smart-post-copy-btn')
14 .getAttribute('data-url');
15
16 if (link && navigator.clipboard) {
17 navigator.clipboard.writeText(link);
18 } else {
19 console.log(
20 'Clipboard API not supported or link missing.'
21 );
22 }
23 });
24 });
25 }
26 };
27
28 // Post Modal custom lightbox.
29 const modal = () => {
30 const modalContainers = document.querySelectorAll(
31 ".sp-smart-post-modal-container"
32 );
33 let imageSize;
34
35 // Utility: Get next or previous post ID based on current
36 const getNextPostId = (direction, currentId, allIds) => {
37 const index = allIds.indexOf(String(currentId));
38 if (index === -1) {
39 return currentId;
40 }
41 if (direction === "next") {
42 return allIds[(index + 1) % allIds.length];
43 } else if (direction === "prev") {
44 return allIds[(index - 1 + allIds.length) % allIds.length];
45 }
46 return currentId;
47 };
48
49 const getReadingTime = (content, attr) => {
50 const text = content.trim();
51 const characterCount = text.length;
52 const wordCount = text.split(/\s+/).filter(Boolean).length;
53 const allCount = {
54 words: wordCount,
55 chars: characterCount,
56 };
57 attr = JSON.parse(attr);
58 const type = attr?.unit;
59 return `${Math.floor(
60 parseInt(allCount[type]) / parseInt(attr.value)
61 )} Min Read`;
62 };
63 const sendPostIdAjax = (postId, modal) => {
64 fetch(sp_smart_post_block_localize.ajaxUrl, {
65 method: "POST",
66 headers: {
67 "Content-Type": "application/x-www-form-urlencoded",
68 },
69 body: new URLSearchParams({
70 action: "sp_handle_post_id",
71 nonce: sp_smart_post_block_localize.ajaxNonce,
72 post_id: postId,
73 image_size: imageSize,
74 }),
75 })
76 .then((res) => res.json())
77 .then((data) => {
78 if (data.success) {
79 const postData = data.data;
80 const modalImage = postData.post_image;
81 const modalTitle = postData.title;
82 const modalAuthor = postData.author_name;
83 const modalDate = postData.date_time;
84 const modalTaxonomy = postData.meta_taxonomy;
85 const modalBadges = postData.post_badges_list;
86 const modalContent = postData.content.replace(
87 /^"|"$/g,
88 ""
89 );
90 const modalComment = postData.post_data.comment_count;
91 const modalViews = postData.post_views;
92 const modalLike = postData.like_button;
93 const readingTimeContainer = modal.querySelector(
94 ".sp-smart-post-reading-time"
95 );
96 const readValue = readingTimeContainer
97 ? readingTimeContainer.dataset.readTime
98 : "";
99 let readTime;
100 if (readValue) {
101 readTime = getReadingTime(
102 modalContent.replace(/<\/?p>/g, ""),
103 readValue
104 );
105 }
106
107 modal.querySelector(
108 ".sp-smart-post-card-modal-image"
109 ).innerHTML = modalImage;
110 modal.querySelector(
111 ".sp-post-modal-title"
112 ).textContent = modalTitle;
113 const authorEl = modal.querySelector(".sp-meta-author");
114 if (authorEl) {
115 authorEl.innerText = modalAuthor;
116 }
117 const dateEl = modal.querySelector(".sp-meta-date");
118 if (dateEl) {
119 dateEl.innerText = modalDate;
120 }
121 const commentsEl =
122 modal.querySelector(".sp-meta-comments");
123 if (commentsEl) {
124 commentsEl.innerText = modalComment;
125 }
126 const viewsEl = modal.querySelector(".sp-meta-views");
127 if (viewsEl) {
128 viewsEl.innerText = modalViews;
129 }
130 const likesEl = modal.querySelector(
131 ".sp-smart-post-likes"
132 );
133 if (likesEl) {
134 likesEl.innerHTML = modalLike;
135 }
136 const readTimeEl = modal.querySelector(
137 ".sp-meta-reading-time"
138 );
139 if (readTimeEl) {
140 readTimeEl.innerHTML = readTime;
141 }
142
143 const postBadgesEl = modal.querySelector(
144 ".sp-title-badges-list"
145 );
146 if ( postBadgesEl && modalBadges && modalBadges?.length > 0 ) {
147 const listItems = modalBadges.map( item => `<li class="sp-title-badge-item">${item.name}</li>`)?.join("");
148 postBadgesEl.innerHTML = listItems;
149 }
150
151 modal.querySelector(
152 ".sp-post-modal-excerpt"
153 ).innerHTML = modalContent;
154 modal.querySelector(
155 ".sp-meta-taxonomy-category"
156 ).innerText = modalTaxonomy;
157
158
159 setTimeout(() => {
160 modal.style.opacity = 1;
161 }, 300);
162 } else {
163 console.log("PHP error:", data.data);
164 }
165 })
166 .catch((err) => {
167 console.log("Fetch error:", err);
168 });
169 };
170
171 const cleanData = (modal) => {
172 modal.querySelector(".sp-smart-post-card-modal-image").innerHTML =
173 "";
174 modal.querySelector(".sp-post-modal-title").textContent = "";
175 modal.querySelector(".sp-meta-taxonomy-category").innerText = "";
176 modal.querySelector(".sp-post-modal-excerpt").innerHTML = "";
177 };
178
179 // Open modal and setup navigation
180 const openModal = (e, container) => {
181 e.preventDefault();
182 let modalData;
183
184 const modal = container.querySelector( ".sp-smart-post-modal-container" );
185
186 imageSize = container.querySelector(
187 ".sp-smart-post-modal-container .sp-smart-post-card-modal-image"
188 ).dataset.imageSize;
189
190 const clickedCard = e.target.closest(".sp-smart-post-card");
191 const clickedTitle = clickedCard?.querySelector(
192 ".sp-smart-post-title-wrapper"
193 );
194
195 if (!clickedTitle) {
196 return;
197 }
198
199 let activePostId = String(clickedTitle.dataset.postId); // Initialize properly
200 const allTitles = container.querySelectorAll(
201 ".sp-smart-post-title-wrapper"
202 );
203 const allIds = [
204 ...new Set(
205 Array.from(allTitles).map((item) => item.dataset.postId)
206 ),
207 ];
208 const nextBtn = container.querySelector(".sp-smart-modal-next");
209 const prevBtn = container.querySelector(".sp-smart-modal-prev");
210
211 // sendPostIdAjax( clickedTitle.dataset.postId, modal );
212 // Setup navigation button handlers
213 const setupNav = (btn, direction) => {
214 if (!btn) {
215 return;
216 }
217 btn.onclick = () => {
218 activePostId = getNextPostId(
219 direction,
220 activePostId,
221 allIds
222 );
223 // modal.style.opacity = 0;
224 sendPostIdAjax(activePostId, modal);
225 };
226 };
227
228 setupNav(nextBtn, "next");
229 setupNav(prevBtn, "prev");
230
231 // modal.style.display = "block";
232 modal.classList.add("is_open");
233 };
234
235 // Close modal
236 const closeModal = (closeBtn) => {
237 const modal = closeBtn.closest(".sp-smart-post-modal-container");
238 // if ( modal ) modal.style.display = "none";
239
240 modal.style.opacity = 0;
241 if (modal) {
242 modal.classList.remove("is_open");
243 }
244 cleanData(modal);
245 };
246
247 // Attach modal close events
248 modalContainers.forEach((modal) => {
249 const box = modal.querySelector(".sp-smart-post-modal-content");
250 const nextBtn = modal.querySelector(".sp-smart-modal-next");
251 const prevBtn = modal.querySelector(".sp-smart-modal-prev");
252 modal
253 .querySelectorAll(".sp-modal-close-btn.cursor")
254 .forEach((btn) => {
255 btn.addEventListener("click", () => closeModal(btn));
256 });
257 modal.addEventListener("click", (event) => {
258 const clickedEl = event.target;
259 // If click is NOT inside box AND not on next/prev buttons
260 if (
261 !box.contains(clickedEl) &&
262 clickedEl !== nextBtn &&
263 clickedEl !== prevBtn &&
264 (!nextBtn || !nextBtn.contains(clickedEl)) &&
265 (!prevBtn || !prevBtn.contains(clickedEl))
266 ) {
267 closeModal(box);
268 }
269 });
270 });
271
272 // Attach open modal triggers using event delegation for better performance
273 modalContainers.forEach((modal) => {
274 if (!modal.textContent.trim()) {
275 return;
276 }
277
278 const container = modal.parentElement;
279
280 container.addEventListener("click", (e) => {
281 // Do not trigger if the click is inside an already open modal.
282 if (e.target.closest(".sp-smart-post-modal-container")) {
283 return;
284 }
285
286 const trigger = e.target.closest(
287 ".sp-smart-post-card-image a, .sp-smart-post-title-wrapper, .sp-smart-post-read-more-button a"
288 );
289
290 if (!trigger) {
291 return;
292 }
293
294 const clickedCard = trigger.closest(".sp-smart-post-card");
295 if (!clickedCard) {
296 return;
297 }
298
299 const clickedTitle = clickedCard.querySelector(
300 ".sp-smart-post-title-wrapper"
301 );
302 if (!clickedTitle || !clickedTitle.dataset.postId) {
303 return;
304 }
305
306 e.preventDefault();
307 modal.style.opacity = 0;
308 sendPostIdAjax(clickedTitle.dataset.postId, modal);
309 openModal(e, container);
310 });
311 });
312 };
313
314 // Initialize image gallery and video players.
315 const initSmartPostFeatures = (context = document) => {
316 // --- Slider Init ---
317 const sliderWrappers = context.querySelectorAll(".sp-slider-wrapper");
318
319 sliderWrappers.forEach(slideWrapper => {
320 if (!slideWrapper.classList.contains("sp-slider-initialized")) {
321 new SPSlider(slideWrapper, {
322 loop: true,
323 autoplay: true,
324 interval: 3000,
325 speed: 600,
326 });
327 slideWrapper.classList.add("sp-slider-initialized");
328 }
329 });
330
331 // --- Video Player Init ---
332 const videoPostList = context.querySelectorAll(".sp-smart-video-player");
333
334 videoPostList.forEach(videoContainer => {
335 if (!videoContainer.classList.contains("sp-video-initialized")) {
336 const videoUrl = videoContainer.dataset.video_url;
337 const featureImage = videoContainer.dataset.image_url;
338 const videoCaption = videoContainer.dataset.video_caption;
339
340 new SpSmartVideoPlayer(videoContainer, videoUrl, {
341 featureImage,
342 videoCaption,
343 showFeatureImage: true,
344 transitionSpeed: 500,
345 });
346 videoContainer.classList.add("sp-video-initialized");
347 }
348 videoContainer.addEventListener("click", function ( event ) {
349 event.preventDefault();
350 event.stopPropagation();
351
352 const featureVideo = videoContainer.querySelector("video");
353 if ( featureVideo ) {
354 if (featureVideo.paused) {
355 featureVideo.play();
356 } else {
357 featureVideo.pause();
358 }
359 }
360 });
361 });
362 }
363
364 initSmartPostFeatures();
365
366
367 socialLinkCopy();
368
369 modal();
370 /**
371 * Carousel Pagination Function.
372 * @param paginationClass
373 */
374 const paginationDotNumber = (paginationClass) => {
375 return {
376 el: paginationClass,
377 clickable: true,
378 renderBullet(index, className) {
379 return (
380 '<span class="' + className + '">' + (index + 1) + '</span>'
381 );
382 },
383 };
384 };
385 /**
386 * Helper to safely parse JSON.
387 * @param str
388 * @param fallback
389 */
390 const safeJSONParse = (str, fallback = {}) => {
391 try {
392 return JSON.parse(str) || fallback;
393 } catch (e) {
394 console.log('Failed to parse JSON:', str, e);
395 return fallback;
396 }
397 };
398
399 /**
400 * Initializes Timeline layout with dynamic height adjustment.
401 */
402 document.querySelectorAll('.post-timeline-three').forEach((containerEl) => {
403 const swiperEl = containerEl.querySelector('.swiper-container');
404 if (!swiperEl) {
405 return;
406 }
407
408 const postContainers = containerEl.querySelectorAll(
409 '.sp-smart-post-timeline-three-post-container'
410 );
411 const wrapper = document.querySelector(
412 '.timeline-three-layout-two .swiper-wrapper'
413 );
414 const timelineBorder = document.querySelector(
415 '.timeline-three-layout-two .sp-smart-post-timeline-border'
416 );
417
418 // Calculate max height from posts.
419 let maxHeight = 0;
420 postContainers.forEach((post) => {
421 maxHeight = Math.max(maxHeight, post.clientHeight);
422 });
423 maxHeight += 60; // Add margin
424
425 // Apply calculated height
426 if (wrapper) {
427 wrapper.style.marginTop = `${maxHeight}px`;
428 }
429 if (timelineBorder) {
430 timelineBorder.style.marginTop = `${maxHeight}px`;
431 }
432
433 const options = safeJSONParse(
434 swiperEl.getAttribute('data-swiper-options')
435 );
436 if (!Object.keys(options).length) {
437 return;
438 }
439
440 new PCPSwiper(swiperEl, options);
441 });
442
443 /**
444 * Initializes thumbnail sliders (layout one).
445 */
446 document
447 .querySelectorAll(
448 '.sp-smart-post-thumbnail-slider.sp-smart-post-block-wrapper'
449 )
450 .forEach((containerEl) => {
451 const [mainSwiperEl, thumbsSwiperEl] =
452 containerEl.querySelectorAll('.swiper-container');
453 const thumbsOptions = safeJSONParse(
454 containerEl
455 .querySelector('.sp-smart-post-swiper2')
456 ?.getAttribute('data-swiper-options')
457 );
458 const mainOptions = safeJSONParse(
459 mainSwiperEl?.getAttribute('data-swiper-options')
460 );
461
462 if (
463 !mainSwiperEl ||
464 !thumbsSwiperEl ||
465 !Object.keys(mainOptions).length
466 ) {
467 return;
468 }
469
470 const thumbsSwiper = new PCPSwiper(thumbsSwiperEl, thumbsOptions);
471 const carouselPagination = mainOptions?.pagination;
472 const paginationClassName = carouselPagination?.el;
473 const numberPagination = mainOptions?.pagination?.paginationType
474 ? true
475 : false;
476 const mainSwiper = new PCPSwiper(mainSwiperEl, {
477 ...mainOptions,
478 thumbs: { swiper: thumbsSwiper },
479 pagination: !numberPagination
480 ? carouselPagination
481 : paginationDotNumber(paginationClassName),
482 });
483 });
484
485 /**
486 * Initializes thumbnail sliders (layout two).
487 */
488 document
489 .querySelectorAll(
490 '.sp-smart-post-block-wrapper.sp-smart-thumbnail-slider-two'
491 )
492 .forEach((containerEl) => {
493 const [mainSwiperEl, thumbsSwiperEl] =
494 containerEl.querySelectorAll('.swiper-container');
495 const thumbsOptions = safeJSONParse(
496 containerEl
497 .querySelector('.sp-smart-post-swiper2')
498 ?.getAttribute('data-swiper-options')
499 );
500 const mainOptions = safeJSONParse(
501 mainSwiperEl?.getAttribute('data-swiper-options')
502 );
503
504 if (
505 !mainSwiperEl ||
506 !thumbsSwiperEl ||
507 !Object.keys(mainOptions).length
508 ) {
509 return;
510 }
511
512 const thumbsSwiper = new PCPSwiper(thumbsSwiperEl, thumbsOptions);
513 const mainSwiper = new PCPSwiper(mainSwiperEl, {
514 ...mainOptions,
515 thumbs: { swiper: thumbsSwiper },
516 });
517 });
518
519 /**
520 * Throttles a function using requestAnimationFrame for performance.
521 * @param {Function} callback The function to throttle.
522 * @return {Function} The throttled function.
523 */
524 const throttle = (callback) => {
525 let ticking = false;
526 return (...args) => {
527 if (!ticking) {
528 window.requestAnimationFrame(() => {
529 callback(...args);
530 ticking = false;
531 });
532 ticking = true;
533 }
534 };
535 };
536
537 /**
538 * Initializes and manages scroll-based animations for all timeline blocks on the page.
539 * Caches DOM elements and uses a single throttled scroll listener for better performance.
540 */
541 const initTimelineScroll = () => {
542 const allTimelinesData = [];
543 document
544 .querySelectorAll('.sp-smart-post-show-scroll-wrapper')
545 .forEach((timelineWrapper) => {
546 const blockId = timelineWrapper.id;
547 if (!blockId) {
548 return;
549 }
550
551 const postCards = timelineWrapper.querySelectorAll(
552 '.sp-smart-post-timeline-one-post-container .sp-smart-indicator-circle'
553 );
554 const indicatorArrows = timelineWrapper.querySelectorAll(
555 '.sp-smart-post-timeline-one-post-container .sp-smart-indicator-arrow'
556 );
557 const timelineContainer = timelineWrapper.querySelector(
558 '.sp-smart-post-timeline-container'
559 );
560
561 // Pre-create or find the style tag for this instance to avoid creating it on every scroll.
562 let styleTag = timelineWrapper.querySelector(
563 'style.sps-dynamic-styles'
564 );
565 if (!styleTag) {
566 styleTag = document.createElement('style');
567 styleTag.className = 'sps-dynamic-styles';
568 timelineWrapper.insertBefore(
569 styleTag,
570 timelineWrapper.firstChild
571 );
572 }
573
574 if (timelineContainer) {
575 allTimelinesData.push({
576 wrapper: timelineWrapper,
577 blockId,
578 postCards,
579 indicatorArrows,
580 timelineContainer,
581 styleTag,
582 });
583 }
584 });
585
586 // 2. Skip if no timelines are found
587 if (allTimelinesData.length === 0) {
588 return;
589 }
590
591 // 3. Create a single, efficient scroll handler for all timelines
592 const handleTimelineScrolls = () => {
593 const scrollY = window.scrollY;
594 const pageHeight = Math.max(document.body.offsetHeight);
595
596 allTimelinesData.forEach((data) => {
597 const wrapperRect = data.wrapper.getBoundingClientRect();
598
599 // Only process timelines currently visible in the viewport (with a buffer)
600 if (
601 wrapperRect.bottom >= 0 &&
602 wrapperRect.top <= window.innerHeight
603 ) {
604 const scrollPosition = scrollY + 200;
605
606 // Indicator activation logic
607 const indicatorActive = (elements) => {
608 elements.forEach((el) => {
609 const elTop =
610 el.getBoundingClientRect().top + scrollY;
611 if (
612 scrollY + 505 > elTop ||
613 scrollPosition + 700 >= pageHeight
614 ) {
615 el.classList.add('active');
616 } else {
617 el.classList.remove('active');
618 }
619 });
620 };
621 indicatorActive(data.postCards);
622 indicatorActive(data.indicatorArrows);
623
624 // Timeline line height calculation logic
625 const timelineRect =
626 data.timelineContainer.getBoundingClientRect();
627 const timelineTop = timelineRect.top + scrollY;
628 let height = 320;
629 let heightUnit = 'px';
630
631 if (
632 timelineRect.bottom - 518 < 0 ||
633 scrollPosition + 700 >= pageHeight
634 ) {
635 height = 100;
636 heightUnit = '%';
637 } else if (
638 scrollPosition > timelineTop &&
639 timelineRect.bottom - 518 > 0
640 ) {
641 height = scrollPosition + 320 - timelineTop;
642 }
643 // Update the dedicated style tag's content, which is more performant than creating new tags.
644 const styleContent = `#${data.blockId} .sp-smart-post-timeline-container::after { height: ${height}${heightUnit} !important; }`;
645 if (data.styleTag.textContent !== styleContent) {
646 data.styleTag.textContent = styleContent;
647 }
648 }
649 });
650 };
651
652 // 4. Add the single throttled listener
653 window.addEventListener('scroll', throttle(handleTimelineScrolls));
654 };
655
656 // Initialize the timeline scroll animations.
657 initTimelineScroll();
658 function updatePaginationData(section, data) {
659 if (!section || !data) {
660 return;
661 }
662 // Update dataset attributes.
663 section.dataset.pages = data.total_pages;
664 section.dataset.current = data.current_page;
665 if (section?.dataset.pages < 2) {
666 section.style.display = 'none';
667 } else {
668 section.style.display = 'flex';
669 }
670 if (section?.dataset.pages < 2) {
671 return false;
672 }
673 updatePaginationUI(section);
674 createPaginationSection(section);
675 }
676 const createPaginationSection = (section) => {
677 const totalPages = parseInt(section.dataset.pages, 10);
678 const currentPage = parseInt(section.dataset.current, 10);
679 const paginationtype = section?.dataset.paginationtype;
680 if (paginationtype == 'pagination') {
681 if (!totalPages || totalPages < 2) {
682 return;
683 }
684
685 const paginationContainer = section.querySelector(
686 '.sp-smart-post-pagination-buttons'
687 );
688 if (!paginationContainer) {
689 return;
690 }
691
692 paginationContainer.innerHTML = ''; // Clear existing buttons
693
694 // Previous Button
695 const prevBtn = document.createElement('a');
696 prevBtn.href = '#';
697 prevBtn.className = 'page-numbers prev';
698 if (currentPage === 1) {
699 prevBtn.classList.add('disabled');
700 }
701 prevBtn.innerHTML = `<i class="sp-icon-left-open"></i> Previous`;
702 paginationContainer.appendChild(prevBtn);
703
704 // Number Buttons
705 for (let i = 1; i <= totalPages; i++) {
706 const pageBtn = document.createElement('a');
707 pageBtn.href = '#';
708 pageBtn.className = 'page-numbers';
709 pageBtn.dataset.page = i;
710 pageBtn.textContent = i;
711
712 if (i === currentPage) {
713 pageBtn.classList.add('current');
714 }
715
716 paginationContainer.appendChild(pageBtn);
717 }
718
719 // Next Button
720 const nextBtn = document.createElement('a');
721 nextBtn.href = '#';
722 nextBtn.className = 'page-numbers next';
723 if (currentPage === totalPages) {
724 nextBtn.classList.add('disabled');
725 }
726 nextBtn.innerHTML = `Next <i class="sp-icon-right-open"></i>`;
727 paginationContainer.appendChild(nextBtn);
728 }
729 };
730
731 const updatePaginationUI = (section) => {
732 const current = parseInt(section.dataset.current, 10);
733 const total = parseInt(section.dataset.pages, 10);
734 const paginationtype = section?.dataset.paginationtype;
735
736 section.querySelectorAll('.page-numbers[data-page]').forEach((link) => {
737 const page = parseInt(link.dataset.page, 10);
738 link.classList.toggle('current', page === current);
739 });
740 const prev = section.querySelector('.page-numbers.prev');
741 const next = section.querySelector('.page-numbers.next');
742 const loadMoreWrapper = section.querySelector(
743 '.sp-smart-post-load-more-button'
744 );
745 if (prev) {
746 prev.classList.toggle('disabled', current <= 1);
747 }
748 if (next) {
749 next.classList.toggle('disabled', current >= total);
750 }
751 // For nav-arrow buttons.
752 const [prevNav, nextNav] = section.querySelectorAll(
753 '.sp-smart-post-grid-nav-arrow-btn'
754 );
755 if (prevNav && nextNav) {
756 prevNav.style.pointerEvents = current <= 1 ? 'none' : 'auto';
757 prevNav.style.opacity = current <= 1 ? '0.5' : '1';
758 nextNav.style.pointerEvents = current >= total ? 'none' : 'auto';
759 nextNav.style.opacity = current >= total ? '0.5' : '1';
760 }
761 if (loadMoreWrapper) {
762 if (current >= total) {
763 const loadMoreWrapperBtn = loadMoreWrapper.querySelector('a');
764 if (loadMoreWrapperBtn) {
765 loadMoreWrapperBtn.style.display = 'none';
766 }
767 } else {
768 loadMoreWrapper.style.display = 'block';
769 }
770 }
771 };
772 function hasFilterCounts(count_data) {
773 if (!count_data || typeof count_data !== 'object') {
774 return false;
775 }
776
777 return Object.entries(count_data).some(([key, value]) => {
778 // Skip pagination
779 if (key === 'pagination') {
780 return false;
781 }
782 // Check if it's a non-empty array
783 return Array.isArray(value) && value.length > 0;
784 });
785 }
786 function getBlockParams() {
787 const url = new URL(window.location.href);
788 const params = new URLSearchParams(url.search);
789 const blockParams = {};
790 for (const [key, value] of params.entries()) {
791 if (
792 key === 'block' ||
793 key.startsWith('tx_') ||
794 key.startsWith('sps_')
795 ) {
796 blockParams[key] = value;
797 }
798 }
799 return blockParams;
800 }
801
802 function updateSelectByParams(params, blockParent) {
803 // Loop through each param and try to update the matching <select>
804 for (const key in params) {
805 if ('sps_page' == key) {
806 const value = params[key];
807 const pagination_section = blockParent.querySelector(
808 '.sp-smart-post-pagination-section'
809 );
810 pagination_section.dataset.current = value;
811 } else if ('sps_search' == key) {
812 const search_input = blockParent.querySelector(
813 `input[name="${key}"]`
814 );
815 const value = params[key];
816 search_input.value = value;
817 } else {
818 const select = blockParent.querySelector(
819 `select[name="${key}"]`
820 );
821 if (select) {
822 const value = params[key];
823 // Set value if option exists.
824 const option = select.querySelector(
825 `option[value="${value}"]`
826 );
827 if (option) {
828 option.disabled = false;
829 option.selected = true;
830 select.value = value;
831 // Optional: trigger change event
832 // select.dispatchEvent(new Event('change'));
833 }
834 }
835 }
836 }
837 }
838
839 /**
840 * Groups slides into swiper-slide wrappers when using specific effects
841 * @param {HTMLElement} containerEl - The swiper container element
842 * @param {Object} sps_swiper_option - Swiper configuration options
843 * @param {string[]} [effectsRequiringWrapping=['fade', 'cube', 'flip']] - Effects that need slide wrapping
844 */
845 function wrapSlidesForEffect(
846 containerEl,
847 sps_swiper_option,
848 effectsRequiringWrapping = ['fade', 'cube', 'flip']
849 ) {
850 // Check if we need to wrap slides
851 if (
852 sps_swiper_option?.effect &&
853 effectsRequiringWrapping.includes(sps_swiper_option.effect)
854 ) {
855 // Get valid slidesPerView (minimum 1)
856 const slidesPerView = Math.max(
857 1,
858 parseInt(sps_swiper_option?.slidesPerView) || 1
859 );
860 // Only proceed if we need multiple slides per view
861 if (slidesPerView > 1) {
862 const swiperWrapper =
863 containerEl.querySelector('.swiper-wrapper');
864 const loopItems =
865 containerEl.querySelectorAll('.sp-slide-item');
866 loopItems.forEach((item) => {
867 item.classList.remove('swiper-slide');
868 });
869 if (swiperWrapper && loopItems.length > 0) {
870 // Create document fragment for better performance
871 const fragment = document.createDocumentFragment();
872 // Group slides into chunks
873 for (let i = 0; i < loopItems.length; i += slidesPerView) {
874 const slideWrapper = document.createElement('div');
875 slideWrapper.className = 'swiper-slide';
876
877 // Add slides to this group
878 const endIndex = Math.min(
879 i + slidesPerView,
880 loopItems.length
881 );
882 for (let j = i; j < endIndex; j++) {
883 slideWrapper.appendChild(
884 loopItems[j].cloneNode(true)
885 );
886 }
887 fragment.appendChild(slideWrapper);
888 }
889
890 // Clear existing content and add new grouped slides
891 swiperWrapper.innerHTML = '';
892 swiperWrapper.appendChild(fragment);
893 }
894 }
895 }
896 }
897 const selected_params = getBlockParams();
898 // Loop over each Smart Post block container.
899
900 //.................................. js error-----------------
901 document.querySelectorAll('.sp-smart-post-wrapper').forEach((container) => {
902 /**
903 * Preloader.
904 * Fades out preloader.
905 */
906 const preloader = container?.querySelector(
907 '.sp-smart-post-show-pro-pre-query .sp-smart-post-preloader'
908 );
909 if (preloader) {
910 // Wait 2.5 seconds before showing fallback.
911 setTimeout(() => {
912 preloader.classList.remove('sp-d-block');
913 preloader.classList.add('sp-d-hidden');
914 container.classList.add('sp-smart-preloader-removed');
915 }, 500);
916 }
917 // Social Links Copy notification.
918 const socialCopyLinks = container.querySelectorAll(
919 '.sp-copy-url-area .sp-smart-post-copy-btn'
920 );
921 if (socialCopyLinks.length > 0) {
922 let classText;
923 socialCopyLinks.forEach((socialCopyLink) => {
924 socialCopyLink.addEventListener('click', (e) => {
925 const copyText = socialCopyLink.querySelector(
926 '.sp-post-url-copy-popup'
927 );
928 // e.preventDefault();
929 copyText.classList.remove('sp-d-hidden');
930 copyText.classList.add('sp-d-block');
931 clearTimeout(classText);
932 classText = setTimeout(() => {
933 copyText.classList.remove('sp-d-block');
934 copyText.classList.add('sp-d-hidden');
935 }, 2000);
936 });
937 });
938 }
939 if (
940 container?.classList.contains('sp-smart-post-thumbnail-slider') ||
941 container?.classList.contains(
942 'sp-smart-post-thumbnail-slider-two'
943 ) ||
944 container?.classList.contains('sp-smart-thumbnail-slider-two')
945 ) {
946 return false;
947 }
948
949 // Closest parent block with specific block class.
950 const blockParent = container.closest(
951 '.wp-block-sp-smart-post-show-smart-post-parent'
952 );
953
954 const builderElement = container.closest(
955 '.sp-smart-post-builder-wrap'
956 );
957
958 const builderTemplateId = builderElement?.dataset?.buildertemplateid || "";
959
960 const blockId = container.dataset.blockid;
961 const page_id = builderTemplateId ? builderTemplateId : container.dataset.pageid || "";
962 const block_location = container.dataset.location || '';
963 if (blockParent && selected_params?.block == blockId) {
964 updateSelectByParams(selected_params, blockParent);
965 }
966 /**
967 * SECTION 1: INIT SWIPER OR MARQUEE SLIDERS
968 */
969 const containerEl = container.querySelector(
970 '.layout-standard, .layout-center, .swiper-container, .sp_marquee-container'
971 );
972
973 let sps_swiper = null;
974 let sps_swiper_option = {};
975 let sps_marquee = null;
976 let sps_marquee_option = {};
977
978 // Initialize Swiper slider if present
979 if (containerEl?.classList.contains('swiper-container')) {
980 const options = safeJSONParse(
981 containerEl.getAttribute('data-swiper-options')
982 );
983
984 if (!Object.keys(options).length) {
985 return;
986 }
987 const paginationOptions = options?.pagination;
988 const paginationClassName = paginationOptions?.el;
989 const isNumberPagination = !!options.pagination?.paginationType;
990 sps_swiper_option = {
991 ...options,
992 pagination: isNumberPagination
993 ? paginationDotNumber(paginationClassName)
994 : paginationOptions,
995 };
996 // if (sps_swiper_option.effect && ['fade', 'cube', 'flip'].includes(sps_swiper_option?.effect)) {
997 // // Get the slidesPerView value (default to 1 if invalid)
998 // let slidesPerView = sps_swiper_option?.slidesPerView > 1 ? sps_swiper_option?.slidesPerView : 1;
999 // let swiperWrapper = containerEl.querySelector('.swiper-wrapper')
1000 // if (slidesPerView > 1) {
1001 // const loopItems = containerEl.querySelectorAll('.sp-slide-item');
1002 // if (loopItems) {
1003 // loopItems.forEach((item) => {
1004 // item.classList.remove('swiper-slide')
1005 // });
1006 // // Create groups of `slidesPerView` items
1007 // for (let i = 0; i < loopItems.length; i += slidesPerView) {
1008 // // Create a new wrapper slide
1009 // const slideWrapper = document.createElement('div');
1010 // slideWrapper.className = 'swiper-slide';
1011
1012 // // Take a slice of `slidesPerView` items and move them into the wrapper
1013 // const itemsToWrap = Array.from(loopItems).slice(i, i + slidesPerView);
1014 // itemsToWrap.forEach(item => {
1015 // slideWrapper.appendChild(item);
1016 // });
1017 // // Insert the wrapper into the container
1018 // swiperWrapper.appendChild(slideWrapper);
1019 // }
1020 // }
1021 // }
1022 // }
1023 if (
1024 sps_swiper_option.effect &&
1025 ['fade', 'cube', 'flip'].includes(sps_swiper_option?.effect)
1026 ) {
1027 wrapSlidesForEffect(containerEl, sps_swiper_option);
1028 }
1029 sps_swiper = new PCPSwiper(containerEl, sps_swiper_option);
1030 }
1031
1032 // Initialize Marquee slider if present
1033 if (containerEl?.classList.contains('sp_marquee-container')) {
1034 sps_marquee_option = safeJSONParse(
1035 containerEl.getAttribute('data-options')
1036 );
1037 if (!Object.keys(sps_marquee_option).length) {
1038 return;
1039 }
1040 sps_marquee_option = {
1041 speed: sps_marquee_option.speed || 500,
1042 direction:
1043 sps_marquee_option.direction === 'left_to_right'
1044 ? 'right'
1045 : 'left',
1046 pauseOnHover: sps_marquee_option.pauseOnHover !== false,
1047 slidesPerView: sps_marquee_option.slidesPerView || 1,
1048 slidesPerViewMobile:
1049 sps_marquee_option.slidesPerViewMobile || 1,
1050 slidesPerViewTablet:
1051 sps_marquee_option.slidesPerViewTablet || 1,
1052 spaceBetween: sps_marquee_option.spaceBetween || 0,
1053 spaceBetweenMobile: sps_marquee_option.spaceBetweenMobile || 0,
1054 spaceBetweenTablet: sps_marquee_option.spaceBetweenTablet || 0,
1055 };
1056 sps_marquee = new SP_Marquee(containerEl, sps_marquee_option);
1057 }
1058
1059 /**
1060 * SECTION 2: FILTER HANDLING
1061 */
1062 const filter_fields =
1063 blockParent?.querySelectorAll(
1064 '.sp-smart-post-live-filter-parent select[name], .sp-smart-post-live-filter-parent input[name]'
1065 ) || [];
1066
1067 const filter_relation =
1068 blockParent?.querySelector('.sp-smart-post-live-filter-parent')
1069 ?.dataset.relation || 'and';
1070
1071 // Fetch filtered content.
1072 const loadQueryData = (state) => {
1073 state.block_location = block_location;
1074 return fetch(sp_smart_post_block_localize.restUrl, {
1075 method: 'POST',
1076 headers: { 'Content-Type': 'application/json' },
1077 body: JSON.stringify(state),
1078 }).then((res) => res.json());
1079 };
1080 // URL update utility.
1081 function updateURLQuery(customParams) {
1082 const baseUrl = new URL(
1083 sp_smart_post_block_localize.permalink_structure ||
1084 window.location.href
1085 );
1086 const customParamsFinal = new URLSearchParams();
1087
1088 if (blockId && Object.values(customParams).some((val) => val)) {
1089 customParamsFinal.append('block', blockId);
1090 }
1091
1092 Object.entries(customParams).forEach(([key, value]) => {
1093 if (value) {
1094 customParamsFinal.append(key, value);
1095 }
1096 });
1097
1098 const joinChar = baseUrl.href.includes('?') ? '&' : '?';
1099 const finalUrl =
1100 baseUrl.href +
1101 (customParamsFinal.toString()
1102 ? joinChar + customParamsFinal.toString()
1103 : '');
1104 window.history.replaceState({}, '', finalUrl);
1105 }
1106
1107 // Build filter state object.
1108 function getFilterState(
1109 event,
1110 update_url = true,
1111 pagination_section = null
1112 ) {
1113 const state = {};
1114 const currentField = event?.target || null;
1115 pagination_section =
1116 pagination_section ??
1117 currentField?.closest('.sp-smart-post-pagination-section');
1118 if (
1119 pagination_section &&
1120 'pagination' == pagination_section?.dataset.paginationtype &&
1121 currentField.tagName === 'A'
1122 ) {
1123 const currentPage = parseInt(
1124 pagination_section.dataset.current,
1125 10
1126 );
1127 newPage = 1;
1128 if (currentField.classList.contains('prev')) {
1129 newPage = currentPage - 1;
1130 } else if (currentField.classList.contains('next')) {
1131 newPage = currentPage + 1;
1132 } else {
1133 newPage = parseInt(currentField.dataset.page, 10);
1134 }
1135 if (newPage > 1) {
1136 state.sps_page = newPage || 1;
1137 }
1138 }
1139 filter_fields.forEach((field) => {
1140 // if (pagination_section && field.tagName === 'DIV') {
1141 // if (field.dataset.current > 1) {
1142 // state['page'] = field.dataset.current || 1;
1143 // }
1144 // } else {
1145 let key = field.name;
1146 const value = field.value.trim();
1147 if (key === 'blog_search') {
1148 key = 'search';
1149 }
1150 state[key] = value;
1151 // }
1152 });
1153
1154 if (update_url) {
1155 updateURLQuery(state);
1156 }
1157 if (Object.keys(state).length > 0) {
1158 state.block = blockId;
1159 state.page_id = page_id;
1160 state.relation = filter_relation;
1161 }
1162 return state;
1163 }
1164
1165 // Initial count load
1166 let currentState = getFilterState(null, false);
1167 function debounce(fn, delay) {
1168 let timer;
1169 return function (...args) {
1170 clearTimeout(timer);
1171 timer = setTimeout(() => fn.apply(this, args), delay);
1172 };
1173 }
1174 const pagination_section = container.querySelector(
1175 '.sp-smart-post-pagination-section'
1176 );
1177 // Load More
1178 const loadMoreWrapper = pagination_section?.querySelector(
1179 '.sp-smart-post-load-more-button'
1180 );
1181 // Listen to filter field changes
1182 if (page_id && filter_fields.length > 0) {
1183 currentState.html = false;
1184 loadQueryData(currentState)
1185 .then((data) => {
1186 if (!data.success) {
1187 return;
1188 }
1189 const termCounts = data.count_data;
1190 if (termCounts && hasFilterCounts(termCounts)) {
1191 updateFilterCounts(termCounts, false, blockParent);
1192 updateAllSmartPostFilterCounts(blockParent);
1193 } else if (Object.keys(currentState).length > 0) {
1194 blockParent
1195 .querySelectorAll(
1196 '.sp-smart-filter-area select option'
1197 )
1198 .forEach((option) => {
1199 const slug = option.value;
1200 if (slug) {
1201 option.textContent = `${capitalize(
1202 slug
1203 )} (0)`;
1204 option.disabled = true;
1205 }
1206 });
1207 } else {
1208 resetFilterCounts(blockParent);
1209 }
1210 initTimelineScroll();
1211 })
1212 .catch();
1213 const eventHandler = (e) => {
1214 currentState = getFilterState(e);
1215 if (preloader) {
1216 // Wait 2.5 seconds before showing fallback
1217 preloader.classList.add('sp-d-block');
1218 container.classList.remove('sp-smart-preloader-removed');
1219 }
1220 let itemsEl = null;
1221 loadQueryData(currentState).then((data) => {
1222 if (!data.success) {
1223 return;
1224 }
1225 if (pagination_section) {
1226 pagination_section.dataset.current = 1;
1227 }
1228 if (
1229 sps_marquee &&
1230 containerEl.classList.contains('sp_marquee-container')
1231 ) {
1232 container.querySelector(
1233 '.sp_marquee-content'
1234 ).innerHTML = data.html;
1235 sps_marquee = new SP_Marquee(
1236 containerEl,
1237 sps_marquee_option
1238 );
1239 } else if (sps_swiper) {
1240 // if (sps_swiper.slides && sps_swiper.slides.length > 0) {
1241 // // sps_swiper.removeAllSlides();
1242 // }
1243 sps_swiper.destroy(true);
1244 container.querySelector('.swiper-wrapper').innerHTML =
1245 data.html;
1246 if (
1247 sps_swiper_option.effect &&
1248 ['fade', 'cube', 'flip'].includes(
1249 sps_swiper_option?.effect
1250 )
1251 ) {
1252 wrapSlidesForEffect(containerEl, sps_swiper_option);
1253 }
1254 sps_swiper = new PCPSwiper(
1255 containerEl,
1256 sps_swiper_option
1257 );
1258 } else {
1259 const items = container.querySelector(
1260 '.sp-smart-post-items'
1261 );
1262 items.innerHTML = data.html;
1263 itemsEl = items;
1264 // container.querySelector(
1265 // '.sp-smart-post-items'
1266 // ).style.opacity = '1';
1267 // 🎯 Staggered Animation
1268 if (itemsEl) {
1269 const childItems = itemsEl.querySelectorAll(
1270 '.sp-smart-post-card, .swiper-slide'
1271 );
1272 childItems.forEach((el, index) => {
1273 el.style.opacity = 0;
1274 el.style.animation = `fadeIn 0.7s ease-in-out forwards`;
1275 el.style.animationDelay = `${index * 0.1}s`; // 80ms stagger
1276 });
1277 }
1278 }
1279 const termCounts = data.count_data;
1280 if (termCounts && hasFilterCounts(termCounts)) {
1281 updateFilterCounts(
1282 termCounts,
1283 pagination_section,
1284 blockParent
1285 );
1286 updateAllSmartPostFilterCounts(blockParent);
1287 } else if (Object.keys(currentState).length > 0) {
1288 blockParent
1289 .querySelectorAll(
1290 '.sp-smart-post-taxonomy-filter select option'
1291 )
1292 .forEach((option) => {
1293 const slug = option.value;
1294 if (slug) {
1295 option.textContent = `${capitalize(
1296 slug
1297 )} (0)`;
1298 option.disabled = true;
1299 }
1300 });
1301 Object.entries(termCounts).forEach(
1302 ([taxonomy, terms]) => {
1303 // Handle pagination update
1304 if (
1305 taxonomy === 'pagination' &&
1306 pagination_section
1307 ) {
1308 updatePaginationData(
1309 pagination_section,
1310 terms
1311 );
1312 }
1313 }
1314 );
1315 updateAllSmartPostFilterCounts(blockParent);
1316 } else {
1317 resetFilterCounts(blockParent);
1318 }
1319 socialLinkCopy();
1320 modal();
1321 initTimelineScroll();
1322 initSmartPostFeatures();
1323 if (preloader) {
1324 // Wait 2.5 seconds before showing fallback
1325 setTimeout(() => {
1326 preloader.classList.remove('sp-d-block');
1327 preloader.classList.add('sp-d-hidden');
1328 container.classList.add(
1329 'sp-smart-preloader-removed'
1330 );
1331 }, 200);
1332 }
1333 if (loadMoreWrapper) {
1334 const no_more_button =
1335 loadMoreWrapper.querySelector('.sps-no-more-post');
1336 if (no_more_button) {
1337 const loadMoreWrapperBTN =
1338 loadMoreWrapper.querySelector('a');
1339 if (loadMoreWrapperBTN) {
1340 loadMoreWrapperBTN.style.display = 'flex';
1341 }
1342 no_more_button.style.display = 'none';
1343 }
1344 }
1345 });
1346 };
1347 filter_fields.forEach((field) => {
1348 const isInput = field.tagName === 'INPUT';
1349 const eventName = isInput ? 'input' : 'change';
1350
1351 // Add event listener.
1352 field.addEventListener(
1353 eventName,
1354 isInput ? debounce(eventHandler, 400) : eventHandler
1355 );
1356 });
1357 }
1358
1359 /**
1360 * SECTION 3: PAGINATION HANDLING
1361 */
1362
1363 if (!pagination_section) {
1364 return;
1365 }
1366 let current = parseInt(pagination_section.dataset.current, 10);
1367 let total = parseInt(pagination_section.dataset.pages, 10);
1368 const endMessage = pagination_section.dataset.endmessage ?? '';
1369 const pagination_type = pagination_section.dataset.paginationtype;
1370 let contents = container.querySelector(
1371 pagination_type === 'load-more'
1372 ? '.sp-smart-post-dynamic-grid-contents,.sp-smart-post-grid-five-static-contents,.sp-smart-post-grid-six-dynamic-contents,.sp-smart-post-timeline-container,.sp-smart-post-small-items,.sp-smart-post-list-one-container'
1373 : '.sp-smart-post-items,.sp-smart-post-list-item'
1374 );
1375
1376 const updateCurrent = (newPage) => {
1377 pagination_section.dataset.current = newPage;
1378 current = newPage;
1379 };
1380 const appendHtml = (html) => {
1381 contents.insertAdjacentHTML('beforeend', html);
1382 return contents;
1383 };
1384
1385 const replaceHtml = (html) => {
1386 contents.innerHTML = html;
1387 return contents;
1388 };
1389
1390 const loadPage = (page, mode = 'replace', currentState = {}) => {
1391 total = parseInt(pagination_section.dataset.pages, 10);
1392 if (page < 1 || page > total || page === current) {
1393 return;
1394 }
1395 contents = container.querySelector(
1396 pagination_type === 'load-more'
1397 ? '.sp-smart-post-dynamic-grid-contents,.sp-smart-post-grid-five-static-contents,.sp-smart-post-grid-six-dynamic-contents,.sp-smart-post-timeline-container,.sp-smart-post-small-items,.sp-smart-post-list-one-container'
1398 : '.sp-smart-post-items,.sp-smart-post-list-item'
1399 );
1400 currentState = {
1401 ...currentState,
1402 sps_page: page,
1403 pagination_type,
1404 block: blockId,
1405 page_id,
1406 };
1407 const infinitePreloader = pagination_section.querySelector(
1408 '.sp-smart-post-show-preloading'
1409 );
1410 const buttonPreloader = pagination_section.querySelector(
1411 '.sp-smart-post-show-preloader'
1412 );
1413
1414 // Show the correct preloader
1415 if (isInfiniteScroll) {
1416 if (infinitePreloader) {
1417 infinitePreloader.style.display = 'block';
1418 }
1419 } else {
1420 if (buttonPreloader) {
1421 // buttonPreloader.style.display = 'block';
1422 buttonPreloader?.classList?.remove('sp-d-hidden');
1423 }
1424 buttonPreloader?.classList?.add('sp-d-block');
1425 }
1426 loadQueryData(currentState)
1427 .then((data) => {
1428 if (!data.success) {
1429 return;
1430 }
1431 if (mode === 'append') {
1432 // Get items count before append.
1433 const existingCount = contents.querySelectorAll(
1434 '.sp-smart-post-card, .swiper-slide'
1435 ).length;
1436 // Append new HTML.
1437 appendHtml(data.html);
1438 // Get all items after append.
1439 const allItems = contents.querySelectorAll(
1440 '.sp-smart-post-card, .swiper-slide'
1441 );
1442 // Animate only the new ones.
1443 allItems.forEach((el, index) => {
1444 if (index >= existingCount) {
1445 el.style.opacity = 0;
1446 el.style.animation = `fadeIn 0.7s ease-in-out forwards`;
1447 el.style.animationDelay = `${
1448 (index - existingCount) * 0.1
1449 }s`;
1450 }
1451 });
1452 } else {
1453 // Replace mode — animate all.
1454 replaceHtml(data.html);
1455 const childItems = contents.querySelectorAll(
1456 '.sp-smart-post-card, .swiper-slide'
1457 );
1458 childItems.forEach((el, index) => {
1459 el.style.opacity = 0;
1460 el.style.animation = `fadeIn 0.7s ease-in-out forwards`;
1461 el.style.animationDelay = `${index * 0.1}s`;
1462 });
1463 }
1464 updateCurrent(page);
1465 updatePaginationUI(pagination_section);
1466 socialLinkCopy();
1467 initSmartPostFeatures();
1468 modal();
1469 initTimelineScroll();
1470 loadMoreWrapper?.classList.remove('sps_disabled');
1471 if (isInfiniteScroll) {
1472 if (infinitePreloader) {
1473 infinitePreloader.style.display = 'none';
1474 }
1475 } else {
1476 if (buttonPreloader) {
1477 // buttonPreloader.style.display = 'none';
1478 buttonPreloader?.classList?.remove('sp-d-block');
1479 }
1480 buttonPreloader?.classList?.add('sp-d-hidden');
1481 }
1482 if (page >= total && loadMoreWrapper) {
1483 loadMoreWrapper.insertAdjacentHTML(
1484 'beforeend',
1485 `<span class="sps-no-more-post">${endMessage}</span>`
1486 );
1487 const loadMoreWrapperBtn =
1488 loadMoreWrapper.querySelector('a');
1489 loadMoreWrapperBtn.style.display = 'none';
1490 }
1491 })
1492 .catch((err) => console.log('Pagination Error:', err));
1493 };
1494
1495 const loadMoreBtn = loadMoreWrapper?.querySelector('a');
1496 const isInfiniteScroll = !!loadMoreWrapper?.querySelector(
1497 '.sp-smart-post-show-preloading'
1498 );
1499
1500 if (isInfiniteScroll && 'IntersectionObserver' in window) {
1501 const observer = new IntersectionObserver(
1502 (entries) => {
1503 if (entries[0].isIntersecting) {
1504 current = parseInt(
1505 pagination_section.dataset.current,
1506 10
1507 );
1508 if (current < total) {
1509 currentState = getFilterState(
1510 null,
1511 true,
1512 pagination_section
1513 );
1514 // Show preloader
1515 const preloader = loadMoreWrapper.querySelector(
1516 '.sp-smart-post-show-preloading'
1517 );
1518 if (preloader) {
1519 preloader.style.display = 'block';
1520 }
1521 loadPage(current + 1, 'append', currentState);
1522 }
1523 }
1524 },
1525 { rootMargin: '100px' }
1526 );
1527 if (loadMoreWrapper) {
1528 observer.observe(loadMoreWrapper);
1529 }
1530 } else if (loadMoreBtn) {
1531 const load_more_preloader = loadMoreWrapper?.querySelector(
1532 '.sp-smart-post-show-preloader'
1533 );
1534 loadMoreBtn.addEventListener('click', (e) => {
1535 e.preventDefault();
1536 // load_more_preloader.style.display = 'block';
1537 load_more_preloader.classList.remove('sp-d-hidden');
1538 load_more_preloader.classList.add('sp-d-block');
1539 // Disable the button
1540 loadMoreWrapper.classList.add('sps_disabled');
1541
1542 // loadMoreBtn.textContent = 'Loading...';
1543 current = parseInt(pagination_section.dataset.current, 10);
1544
1545 currentState = getFilterState(e, false);
1546
1547 loadPage(current + 1, 'append', currentState);
1548 });
1549 }
1550 const paginationContainer = pagination_section.querySelector(
1551 '.sp-smart-post-pagination-buttons'
1552 );
1553 paginationContainer?.addEventListener('click', (e) => {
1554 const btn = e.target.closest('.page-numbers');
1555 if (
1556 !btn ||
1557 btn.classList.contains('disabled') ||
1558 btn.classList.contains('current')
1559 ) {
1560 return;
1561 }
1562 e.preventDefault();
1563 const currentPage = parseInt(
1564 pagination_section.dataset.current,
1565 10
1566 );
1567 let newPage = null;
1568
1569 if (btn.classList.contains('prev')) {
1570 newPage = currentPage - 1;
1571 } else if (btn.classList.contains('next')) {
1572 newPage = currentPage + 1;
1573 } else {
1574 newPage = parseInt(btn.dataset.page, 10);
1575 }
1576
1577 if (isNaN(newPage) || newPage === currentPage) {
1578 return;
1579 }
1580 // Get current filter state (if you have one).
1581
1582 const currentState = getFilterState(e);
1583 // Load the new page.
1584
1585 loadPage(newPage, 'replace', currentState);
1586 // Optional: update the data-current attribute.
1587
1588 pagination_section.dataset.current = newPage;
1589 });
1590 // Grid Nav Arrows (custom arrow buttons)
1591 const navArrows = pagination_section.querySelectorAll(
1592 '.sp-smart-post-grid-nav-arrow-btn'
1593 );
1594 if (navArrows.length === 2) {
1595 const [prevArrow, nextArrow] = navArrows;
1596 prevArrow.addEventListener('click', (e) => {
1597 e.preventDefault();
1598 currentState = getFilterState(e);
1599 loadPage(current - 1, 'replace', currentState);
1600 });
1601 nextArrow.addEventListener('click', (e) => {
1602 e.preventDefault();
1603 currentState = getFilterState(e);
1604 current = parseInt(pagination_section.dataset.current, 10);
1605 loadPage(current + 1, 'replace', currentState);
1606 });
1607 }
1608
1609 updatePaginationUI(pagination_section);
1610 });
1611 //.................................. js error-----------------
1612
1613 /**
1614 * Capitalizes the first letter of a string.
1615 *
1616 * @param {string} str - The string to capitalize.
1617 * @return {string} - Capitalized string.
1618 */
1619 function capitalize(str) {
1620 if (!str) {
1621 return '';
1622 }
1623 return str.charAt(0).toUpperCase() + str.slice(1);
1624 }
1625
1626 /**
1627 * Updates the filter <select> options with post counts from the response.
1628 *
1629 * @param {Object} termCounts - Term count data (taxonomy → terms[]).
1630 * @param {HTMLElement|null} pagination_section - Pagination DOM section (if exists).
1631 * @param {HTMLElement} blockParent - The block wrapper containing filters.
1632 */
1633 function updateFilterCounts(termCounts, pagination_section, blockParent) {
1634 if (!termCounts || !blockParent) {
1635 return;
1636 }
1637 const show_count = true;
1638 Object.entries(termCounts).forEach(([taxonomy, terms]) => {
1639 // Handle pagination update
1640 if (taxonomy === 'pagination' && pagination_section) {
1641 updatePaginationData(pagination_section, terms);
1642 return;
1643 }
1644
1645 // Find the filter select dropdown for the taxonomy
1646 const select = blockParent.querySelector(`#filter-${taxonomy}`);
1647 if (!select) {
1648 return;
1649 }
1650
1651 Array.from(select.options).forEach((option) => {
1652 const slug = option.value;
1653 if (!slug || !terms) {
1654 return;
1655 }
1656 const isSelected = option.selected;
1657 const defaultLabel = option.getAttribute('data-label') || slug;
1658 const term = terms.find((t) => t.slug == slug);
1659 if (term) {
1660 const count = show_count ? `(${term.count})` : '';
1661 // Term exists: show its updated count.
1662 option.textContent = `${capitalize(defaultLabel)} ${count}`;
1663 option.disabled = false;
1664 } else {
1665 // Term not in result: disable unless selected.
1666 const count = show_count ? `(0)` : '';
1667 option.textContent = `${capitalize(defaultLabel)} (0)`;
1668 option.disabled = !isSelected;
1669 }
1670 });
1671 });
1672 }
1673
1674 /**
1675 * Resets all filter dropdowns to their default counts (used when no filters are active).
1676 *
1677 * @param {HTMLElement} blockParent - The block wrapper containing filters.
1678 */
1679 function resetFilterCounts(blockParent) {
1680 if (!blockParent) {
1681 return;
1682 }
1683
1684 blockParent
1685 .querySelectorAll('.sp-smart-filter-area select option')
1686 .forEach((option) => {
1687 const slug = option.value;
1688 if (!slug) {
1689 return;
1690 } // Skip the "All" or blank option
1691
1692 const defaultCount = option.getAttribute('data-default-count');
1693 if (defaultCount !== null) {
1694 option.textContent = `${capitalize(
1695 option.getAttribute('data-label') || slug
1696 )} (${defaultCount})`;
1697 option.disabled = false;
1698 }
1699 });
1700 }
1701 //-------------------------------------
1702
1703 const filterWrappers = document.querySelectorAll(
1704 ".sp-smart-post-taxonomy-filter, .sp-smart-post-sort-filter"
1705 );
1706
1707 // Common handler function for dropdown items
1708 function handleDropdownClick(
1709 e,
1710 dropdown,
1711 oppositeDropdown,
1712 spanLabel,
1713 select
1714 ) {
1715 const item = e.target.closest("a");
1716 if (!item || item.classList.contains("disabled")) return;
1717
1718 e.preventDefault();
1719
1720 // Remove active class from opposite dropdown if exists
1721 if (oppositeDropdown) {
1722 const links = oppositeDropdown.querySelectorAll(
1723 ".sps-live-filter-nav-link"
1724 );
1725 links.forEach((link) => link.classList.remove("active"));
1726 }
1727
1728 // Remove active from all items in current dropdown
1729 dropdown
1730 .querySelectorAll("a")
1731 .forEach((el) => el.classList.remove("active"));
1732
1733 // Add active to clicked item
1734 item.classList.add("active");
1735
1736 // Extract values
1737 const value = item.getAttribute("data-value");
1738 const label = item.getAttribute("data-label");
1739 const countMatch = item.textContent.match(/\((\d+)\)/);
1740 const count = countMatch ? countMatch[1] : "";
1741
1742 // Update button label
1743 if (spanLabel) {
1744 spanLabel.innerHTML = `${label}${count ? ` (${count})` : ""}`;
1745 }
1746
1747 // Update select and trigger change
1748 if (select) {
1749 select.value = value;
1750 select.dispatchEvent(new Event("change"));
1751 }
1752
1753 // Close dropdown
1754 dropdown.classList.remove("open");
1755 }
1756
1757 // Setup function for each filter wrapper
1758 function setupFilterWrapper(wrapper) {
1759 const btn = wrapper.querySelector(".sp-smart-post-live-filter-btn");
1760 const dropdown = wrapper.querySelector(
1761 ".sp-smart-post-live-filter-dropdown, .sp-smart-post-live-filter-button"
1762 );
1763 const secondDropdown = wrapper.querySelector(
1764 ".sps-live-filter-dropdown-menu"
1765 );
1766 const spanLabel = btn?.querySelector("span");
1767
1768 // Find the closest hidden select
1769 const select = wrapper
1770 .closest(".sp-smart-post-live-filter-wrapper")
1771 ?.querySelector("select");
1772
1773 if (!select) {
1774 return;
1775 }
1776
1777 // Setup dropdown toggle
1778 if (btn && dropdown) {
1779 btn.addEventListener("click", (e) => {
1780 e.preventDefault();
1781 dropdown.classList.toggle("open");
1782 });
1783 }
1784
1785 // Setup dropdown click handlers
1786 if (dropdown) {
1787 dropdown.addEventListener("click", (e) =>
1788 handleDropdownClick(
1789 e,
1790 dropdown,
1791 secondDropdown,
1792 spanLabel,
1793 select
1794 )
1795 );
1796 }
1797
1798 if (secondDropdown) {
1799 secondDropdown.addEventListener("click", (e) =>
1800 handleDropdownClick(
1801 e,
1802 secondDropdown,
1803 dropdown,
1804 spanLabel,
1805 select
1806 )
1807 );
1808 }
1809 }
1810
1811 // Initialize all filter wrappers
1812 filterWrappers.forEach(setupFilterWrapper);
1813
1814 // Single event listener for closing dropdowns on outside click
1815 document.addEventListener("click", (e) => {
1816 document
1817 .querySelectorAll(".sp-smart-post-live-filter-dropdown.open")
1818 .forEach((dropdown) => {
1819 const btn = dropdown.previousElementSibling;
1820 if (
1821 !dropdown.contains(e.target) &&
1822 (!btn || !btn.contains(e.target))
1823 ) {
1824 dropdown.classList.remove("open");
1825 }
1826 });
1827 });
1828 //---------------------------------------
1829 /**
1830 * Utility: Wait until all images inside an element are loaded before running a callback.
1831 *
1832 * @param {HTMLElement} el - The container element to monitor.
1833 * @param {Function} callback - Function to call once all images are loaded.
1834 */
1835 const imagesLoaded = (el, callback) => {
1836 const images = el.querySelectorAll("img");
1837 let loadedCount = 0;
1838
1839 // No images? Run callback immediately
1840 if (images.length === 0) {
1841 return callback();
1842 }
1843
1844 images.forEach((img) => {
1845 if (img.complete) {
1846 loadedCount++;
1847 if (loadedCount === images.length) {
1848 callback();
1849 }
1850 } else {
1851 img.onload = img.onerror = () => {
1852 loadedCount++;
1853 if (loadedCount === images.length) {
1854 callback();
1855 }
1856 };
1857 }
1858 });
1859 };
1860
1861 /**
1862 * Utility: Get the unique block ID from an element's closest wrapper.
1863 *
1864 * @param {HTMLElement} child - A child element inside the block.
1865 * @return {string|null} - Block ID or null.
1866 */
1867 const getBlockIdFromChild = (child) => {
1868 const wrapper = child.closest(".sp-smart-post-wrapper");
1869 return wrapper ? wrapper.id : null;
1870 };
1871
1872 /**
1873 * Equal height logic for carousel items (Swiper cards).
1874 */
1875 const carouselContainers = document.querySelectorAll(
1876 '.sp-smart-post-swiper.sp-equal-height-wrapper'
1877 );
1878
1879 carouselContainers.forEach((carousel) => {
1880 const containerId = getBlockIdFromChild(carousel);
1881 const equalHeightEnabled = carousel.dataset.equalHeight == '1';
1882 let container = document.getElementById(containerId);
1883
1884 // Check inside iframe (used in editors like block preview)
1885 if (!container) {
1886 const iframe = document.querySelector('iframe');
1887 const iframeDoc = iframe?.contentWindow?.document;
1888 container = iframeDoc?.getElementById(containerId) || null;
1889 }
1890
1891 if (container) {
1892 const cards = container.querySelectorAll('.sp-smart-post-card');
1893
1894 const applyEqualHeight = () => {
1895 if (!cards.length) {
1896 return;
1897 }
1898
1899 if (equalHeightEnabled) {
1900 // Calculate max height
1901 let maxHeight = 0;
1902 cards.forEach((card) => {
1903 card.style.height = 'auto'; // reset first
1904 const height = card.offsetHeight;
1905 if (height > maxHeight) {
1906 maxHeight = height;
1907 }
1908 });
1909 // Apply max height to all cards
1910 cards.forEach((card) => {
1911 card.style.height = `${maxHeight}px`;
1912 });
1913 } else {
1914 // Reset to auto
1915 cards.forEach((card) => {
1916 card.style.height = 'auto';
1917 });
1918 }
1919 };
1920 // Apply after all images load
1921 imagesLoaded(container, applyEqualHeight);
1922 }
1923 });
1924 // Update selection count field.
1925 update_selection_field_count();
1926
1927 const containers = document.querySelectorAll('.sps-live-filter-layout-two');
1928
1929 containers.forEach((container) => {
1930 const style = container.dataset.style;
1931 let currentPage = parseInt(container.dataset.currentPage);
1932 let isDropdownOpen = container.dataset.isDropdownOpen === 'true';
1933 const totalPages = parseInt(container.dataset.totalPages);
1934
1935 const dropdown = container.querySelector('.sps-live-filter-dropdown');
1936 const dropdownList = container.querySelector('.sps-live-filter-dropdown-menu');
1937 if ( dropdownList && dropdownList?.children.length === 0 ) {
1938 dropdown.style.display = 'none';
1939 }
1940
1941 // State management functions
1942 function updateState() {
1943 // container.dataset.activeItem = activeItem;
1944 container.dataset.currentPage = currentPage;
1945 container.dataset.isDropdownOpen = isDropdownOpen;
1946 }
1947
1948 function toggleDropdown() {
1949 isDropdownOpen = !isDropdownOpen;
1950 updateState();
1951 updateUI();
1952 }
1953
1954 function closeDropdown() {
1955 isDropdownOpen = false;
1956 updateState();
1957 updateUI();
1958 }
1959
1960 function nextPage() {
1961 if (currentPage < totalPages - 1) {
1962 currentPage++;
1963 updateState();
1964 }
1965 }
1966
1967 function prevPage() {
1968 if (currentPage > 0) {
1969 currentPage--;
1970 updateState();
1971 // updatePage();
1972 }
1973 }
1974
1975 // UI update function
1976 function updateUI() {
1977 // Update dropdown visibility
1978 if (dropdown) {
1979 if (isDropdownOpen) {
1980 dropdown.classList.add('active');
1981 } else {
1982 dropdown.classList.remove('active');
1983 }
1984 }
1985 }
1986
1987 // Event handlers - using event delegation
1988 container.addEventListener('click', function (e) {
1989 // Handle term clicks (including dynamically created ones)
1990 if (
1991 e.target.closest('.sps-live-filter-nav-link') &&
1992 !e.target.closest('.sps-live-filter-dropdown')
1993 ) {
1994 e.preventDefault();
1995 const id = e.target.closest('.sps-live-filter-nav-link').dataset
1996 .id;
1997 // setActiveItem(id);
1998 }
1999
2000 // Handle dropdown toggle
2001 if (
2002 e.target.closest(
2003 '.sps-live-filter-dropdown .sps-live-filter-nav-link'
2004 )
2005 ) {
2006 e.preventDefault();
2007 if (style !== 'navigation') {
2008 toggleDropdown();
2009 }
2010 }
2011
2012 // Handle dropdown item clicks
2013 if (e.target.closest('.sps-live-filter-dropdown-item')) {
2014 e.preventDefault();
2015 const id = e.target.closest('.sps-live-filter-dropdown-item')
2016 .dataset.id;
2017 // setActiveItem(id);
2018 }
2019
2020 // Handle pagination clicks.
2021 if (e.target.closest('.sps-pagination-prev')) {
2022 e.preventDefault();
2023 prevPage();
2024 }
2025
2026 if (e.target.closest('.sps-pagination-next')) {
2027 e.preventDefault();
2028 nextPage();
2029 }
2030 });
2031
2032 // Close dropdown when clicking outside.
2033 document.addEventListener('click', function (e) {
2034 if (!container.contains(e.target)) {
2035 closeDropdown();
2036 }
2037 });
2038 });
2039 });
2040
2041 /**
2042 * Initializes or updates all smart filter dropdowns on the page.
2043 */
2044 function update_selection_field_count() {
2045 document
2046 .querySelectorAll('.sp-smart-post-live-filter-wrapper')
2047 .forEach((wrapper) => {
2048 const selectEl = wrapper.querySelector('select');
2049 const dropdownUl = wrapper.querySelector(
2050 '.sp-smart-post-live-filter-dropdown, .sp-smart-post-live-filter-button'
2051 );
2052 const moreButton = wrapper.querySelector(
2053 '.sps-live-filter-dropdown-menu'
2054 );
2055 buildSmartFilterDropdown(selectEl, dropdownUl, moreButton);
2056 });
2057 }
2058
2059 function buildSmartFilterDropdown(selectEl, dropdownUl, moreButton) {
2060 if (!selectEl || !dropdownUl) return;
2061
2062 // Clear existing dropdown items
2063 dropdownUl.innerHTML = '';
2064 if (moreButton) moreButton.innerHTML = '';
2065
2066 const options = Array.from(selectEl.options);
2067
2068 let attributes = {};
2069 try {
2070 const dataAtt = selectEl.dataset.att;
2071 attributes =
2072 dataAtt && dataAtt !== 'undefined' ? JSON.parse(dataAtt) : {};
2073 } catch (e) {
2074 console.warn('Invalid JSON in data-att:', e);
2075 attributes = {};
2076 }
2077 const { taxonomyLimit, taxonomyStyle, filterType, uniqueId } = attributes;
2078
2079 let device = 'Desktop';
2080
2081 if (window.innerWidth <= 781) device = 'Mobile';
2082 else if (window.innerWidth <= 1024) device = 'Tablet';
2083
2084 // Safely access taxonomyLimit.device
2085 const limit =
2086 taxonomyLimit && taxonomyLimit.device
2087 ? taxonomyLimit.device[device] || 3
2088 : 3;
2089
2090 if (taxonomyStyle === 'navigation') {
2091 // Initialize pagination state
2092 let currentPage = parseInt(dropdownUl.dataset.currentPage) || 1;
2093 const totalPages = Math.ceil(options.length / limit);
2094
2095 // Ensure current page is within valid range
2096 currentPage = Math.max(1, Math.min(currentPage, totalPages));
2097 dropdownUl.dataset.currentPage = currentPage;
2098
2099 // Calculate current page items
2100 const startIndex = (currentPage - 1) * limit;
2101 const endIndex = startIndex + limit;
2102 const currentOptions = options.slice(startIndex, endIndex);
2103
2104 // Add current page options to dropdown
2105 currentOptions.forEach((option) => {
2106 const value = option.value;
2107 if (value === undefined) return;
2108
2109 const label =
2110 option.getAttribute('data-label') || option.textContent.trim();
2111 const count = option.getAttribute('data-default-count') || '';
2112 const isSelected = option.selected ?? false;
2113 const isDisabled = parseInt(count) === 0;
2114
2115 const li = document.createElement('li');
2116 const a = document.createElement('a');
2117 li.classList.add('sps-live-filter-nav-item');
2118 a.classList.add('sps-live-filter-nav-link');
2119
2120 a.href = '#';
2121 a.setAttribute('data-value', value);
2122 a.setAttribute('data-label', label);
2123 a.textContent = `${label}${count ? ` (${count})` : ''}`;
2124
2125 // if (!count && selectEl.id !== "filter-sort") {
2126 // a.classList.add("active");
2127 // }
2128
2129 li.appendChild(a);
2130 if ( label ) {
2131 dropdownUl.appendChild(li);
2132 }
2133 });
2134
2135 // Create pagination controls
2136 const paginationContainer = document.createElement('div');
2137 paginationContainer.className = 'pagination-controls';
2138
2139 // const container = document.querySelector(".sps-live-filter-layout-two");
2140 const container = document.querySelector(`#${uniqueId}`);
2141 const prev = container.querySelector('.sps-pagination-prev');
2142 const next = container.querySelector('.sps-pagination-next');
2143
2144 prev.addEventListener('click', () => {
2145 dropdownUl.dataset.currentPage = currentPage - 1;
2146 buildSmartFilterDropdown(selectEl, dropdownUl, moreButton);
2147 });
2148
2149 // Page indicator
2150 const pageIndicator = document.createElement('span');
2151 pageIndicator.className = 'pagination-info';
2152 pageIndicator.textContent = `Page ${currentPage} of ${totalPages}`;
2153
2154 next.addEventListener('click', () => {
2155 dropdownUl.dataset.currentPage = currentPage + 1;
2156 buildSmartFilterDropdown(selectEl, dropdownUl, moreButton);
2157 });
2158 } else if (moreButton) {
2159 // Non-navigation style with more button
2160 const optionsOne = options.slice(0, limit);
2161 const optionsTwo = options.slice(limit);
2162
2163 // Add first set of options to dropdown
2164
2165 if (filterType !== 'dropdown') {
2166 optionsOne.forEach((option) => {
2167 const value = option.value;
2168 if (value === undefined) return;
2169
2170 const label =
2171 option.getAttribute('data-label') ||
2172 option.textContent.trim();
2173 const count = option.getAttribute('data-default-count') || '';
2174 const li = document.createElement('li');
2175 const a = document.createElement('a');
2176 li.classList.add('sps-live-filter-nav-item');
2177 a.classList.add('sps-live-filter-nav-link');
2178 if ( label === "All" ) {
2179 a.classList.add("active");
2180 }
2181
2182 a.href = '#';
2183 a.setAttribute('data-value', value);
2184 a.setAttribute('data-label', label);
2185 a.textContent = `${label}${count ? ` (${count})` : ''}`;
2186
2187 li.appendChild(a);
2188 if ( label ) {
2189 dropdownUl.appendChild(li);
2190 }
2191 });
2192 }
2193
2194 // Add remaining options to more button
2195 Array.from(filterType === 'dropdown' ? options : optionsTwo).forEach(
2196 (option) => {
2197 const value = option.value;
2198 if (value === undefined) return;
2199 const label =
2200 option.getAttribute('data-label') ||
2201 option.textContent.trim();
2202
2203 const a = document.createElement('a');
2204 a.classList.add('sps-live-filter-nav-link');
2205 a.href = '#';
2206 a.setAttribute('data-value', value);
2207 a.setAttribute('data-label', label);
2208 a.textContent = label;
2209
2210 moreButton.appendChild(a);
2211 }
2212 );
2213 } else {
2214 // No more button - add all options to dropdown
2215 options.forEach((option) => {
2216 const value = option.value;
2217 if (value === undefined) return;
2218
2219 const label =
2220 option.getAttribute('data-label') || option.textContent.trim();
2221 const count = option.getAttribute('data-default-count') || '';
2222
2223 const li = document.createElement('li');
2224 const a = document.createElement('a');
2225 li.classList.add('sps-live-filter-nav-item');
2226 a.classList.add('sps-live-filter-nav-link');
2227
2228 a.href = '#';
2229 a.setAttribute('data-value', value);
2230 a.setAttribute('data-label', label);
2231 a.textContent = `${label}${count ? ` (${count})` : ''}`;
2232
2233 if (!count && selectEl.id !== 'filter-sort') {
2234 a.classList.add('active');
2235 }
2236
2237 li.appendChild(a);
2238 dropdownUl.appendChild(li);
2239 });
2240 }
2241 }
2242
2243 //................................ second layout part 2...................................................................
2244
2245 /**
2246 * Updates all custom dropdowns and their labels based on selected values.
2247 *
2248 * @param {HTMLElement} blockParent - The parent block element containing filter wrappers.
2249 */
2250 function updateAllSmartPostFilterCounts(blockParent) {
2251 const wrappers = blockParent.querySelectorAll(
2252 '.sp-smart-post-live-filter-wrapper'
2253 );
2254
2255 wrappers.forEach((wrapper) => {
2256 const select = wrapper.querySelector('select');
2257 const dropdown = wrapper.querySelector(
2258 '.sp-smart-post-live-filter-dropdown, .sp-smart-post-live-filter-button'
2259 );
2260 const selectedButton = wrapper.querySelector(
2261 '.sp-smart-post-live-filter-btn span'
2262 );
2263 const this_filter = wrapper.querySelector('.sp-smart-post-live-filter');
2264 if (select.id === 'filter-sort') {
2265 // Build map: value → { label, count }
2266 const optionMap = Array.from(select.options).reduce(
2267 (map, option) => {
2268 const value = option.value;
2269 const label =
2270 option.getAttribute('data-label') ||
2271 option.textContent.trim();
2272 // Extract count from text, fallback to 0
2273 const isSelected = option.selected ?? false;
2274 map[value] = { label, isSelected };
2275 return map;
2276 },
2277 {}
2278 );
2279 dropdown.querySelectorAll('a').forEach((a) => {
2280 const value = a.getAttribute('data-value');
2281 const { label, isSelected } = optionMap[value];
2282 if (isSelected) {
2283 dropdown
2284 .querySelectorAll('a')
2285 .forEach((el) => el.classList.remove('active'));
2286 a.classList.add('active');
2287 }
2288 });
2289 // Update selected button label
2290 if (select.value && selectedButton) {
2291 const { label, count } = optionMap[select.value] || {};
2292 if (label !== undefined) {
2293 selectedButton.textContent = `${label}`;
2294 }
2295 }
2296 }
2297
2298 if (!select || !dropdown || select.id === 'filter-sort') {
2299 return;
2300 }
2301 const show_count = this_filter?.getAttribute('data-show-count');
2302 // Build map: value → { label, count }
2303 const optionMap = Array.from(select.options).reduce((map, option) => {
2304 const value = option.value;
2305 const label =
2306 option.getAttribute('data-label') || option.textContent.trim();
2307 // Extract count from text, fallback to 0
2308 const countMatch = option.textContent.trim().match(/\((\d+)\)/);
2309 const count = countMatch ? countMatch[1] : '0';
2310 const isSelected = option.selected ?? false;
2311 map[value] = { label, count, isSelected };
2312 return map;
2313 }, {});
2314
2315 // Update selected button label
2316 if (select.value && selectedButton) {
2317 const { label, count } = optionMap[select.value] || {};
2318 const count_text = show_count ? `(${count})` : '';
2319 if (label !== undefined) {
2320 selectedButton.textContent = `${label} ${count_text}`;
2321 }
2322 }
2323
2324 // Update dropdown items with latest labels and counts
2325 dropdown.querySelectorAll('a').forEach((a) => {
2326 const value = a.getAttribute('data-value');
2327 if (!value || !optionMap[value]) {
2328 return;
2329 }
2330
2331 const { label, count, isSelected } = optionMap[value];
2332
2333 a.setAttribute('data-label', label);
2334 a.setAttribute('data-default-count', count);
2335 const count_text = show_count ? `(${count})` : '';
2336 a.textContent = `${label} ${count_text}`;
2337 if (parseInt(count) === 0) {
2338 a.classList.add('disabled');
2339 a.setAttribute('aria-disabled', 'true');
2340 } else {
2341 a.classList.remove('disabled');
2342 a.removeAttribute('aria-disabled');
2343 if (isSelected) {
2344 dropdown
2345 .querySelectorAll('a')
2346 .forEach((el) => el.classList.remove('active'));
2347 a.classList.add('active');
2348 }
2349 }
2350 });
2351 });
2352 }
2353
2354 // This class handles the sp_marquee functionality
2355 // It can be used to create a scrolling sp_marquee effect for any container with items
2356 // The class takes a container element and options for speed, direction, and pause on hover.
2357 class SP_Marquee {
2358 constructor(container, options = {}) {
2359 this.container =
2360 typeof container === 'string'
2361 ? document.querySelector(container)
2362 : container;
2363
2364 this.options = {
2365 speed: 500, // pixels per second
2366 direction: 'left', // 'left', 'right', 'up', 'down'
2367 pauseOnHover: true,
2368 duplicateItems: true,
2369 slidesPerView: 3,
2370 slidesPerViewTablet: 2,
2371 slidesPerViewMobile: 1,
2372 spaceBetween: 24,
2373 spaceBetweenTablet: 16,
2374 spaceBetweenMobile: 8,
2375 ...options,
2376 };
2377
2378 this.content = this.container.querySelector('.sp_marquee-content');
2379 this.items = Array.from(this.content.children);
2380 this.isPaused = false;
2381 this.itemWidth = 300; // Default width for items, can be adjusted.
2382
2383 this.init();
2384 }
2385 getCurrentBreakpoint() {
2386 const width = window.innerWidth;
2387 if (width <= 600) {
2388 return 'mobile';
2389 }
2390 if (width <= 1024) {
2391 return 'tablet';
2392 }
2393 return 'desktop';
2394 }
2395
2396 init() {
2397 const containerWidth = this.container.offsetWidth;
2398 // Duplicate until content is longer than container by at least one screen.
2399 if (this.options.duplicateItems) {
2400 // while (this.content.scrollWidth < containerWidth * 2) {
2401 // console.log(this.content.scrollWidth);
2402 // this.items.forEach((item) => {
2403 // const clone = item.cloneNode(true);
2404 // this.content.appendChild(clone);
2405 // });
2406 // }
2407 const originalChildren = Array.from(this.items);
2408 // Take the first 3 (or less if not enough items)
2409 const clones = originalChildren.slice(0, this.slidesPerView || 3);
2410 clones.forEach((item) => {
2411 const clone = item.cloneNode(true);
2412 clone.setAttribute('data-marquee-clone', 'true');
2413 this.content.appendChild(clone);
2414 });
2415 }
2416
2417 // Set animation direction and speed
2418 this.setDirection(this.options.direction);
2419
2420 this.setSpeed(this.options.speed);
2421 // Add hover events
2422 if (this.options.pauseOnHover) {
2423 this.container.addEventListener('mouseenter', () => this.pause());
2424 this.container.addEventListener('mouseleave', () => this.resume());
2425 }
2426 window.addEventListener('resize', () => {
2427 this.setSlidesPerView();
2428 this.updateAnimation();
2429 });
2430 }
2431 // setSlidesPerView() {
2432 // this.options.slidesPerView = this.options.slidesPerView || 3;
2433 // const containerWidth = this.container.offsetWidth;
2434 // // Calculate item width based on slidesPerView
2435 // console.log(this.options.slidesPerView);
2436 // const itemWidth = containerWidth / this.options.slidesPerView;
2437 // this.items.forEach(item => {
2438 // item.style.width = `${itemWidth}px`;
2439 // });
2440
2441 // }
2442 setSlidesPerView() {
2443 const breakpoint = this.getCurrentBreakpoint();
2444
2445 const slidesPerView = {
2446 desktop: this.options.slidesPerView,
2447 tablet: this.options.slidesPerViewTablet,
2448 mobile: this.options.slidesPerViewMobile,
2449 }[breakpoint];
2450
2451 const spaceBetween = {
2452 desktop: this.options.spaceBetween,
2453 tablet: this.options.spaceBetweenTablet,
2454 mobile: this.options.spaceBetweenMobile,
2455 }[breakpoint];
2456 this.spaceBetween = spaceBetween;
2457 const containerWidth = this.container.offsetWidth;
2458 const totalSpacing = spaceBetween * (slidesPerView - 1);
2459 const itemWidth = (containerWidth - totalSpacing) / slidesPerView;
2460
2461 this.itemWidth = itemWidth; // Store item width for later use
2462 this.slidesPerView = slidesPerView;
2463 this.items.forEach((item, index) => {
2464 item.style.width = `${this.itemWidth}px`;
2465 item.style.marginRight =
2466 index !== this.items.length - 1 ? `${spaceBetween}px` : "0";
2467 });
2468 }
2469 setDirection(direction) {
2470 // Remove previous direction classes.
2471 this.content.classList.remove(
2472 "sp_marquee-left",
2473 "sp_marquee-right",
2474 "sp_marquee-up",
2475 "sp_marquee-down"
2476 );
2477
2478 // Add new direction class
2479 this.content.classList.add(`sp_marquee-${direction}`);
2480
2481 // Update animation
2482 this.updateAnimation();
2483 }
2484
2485 // setSpeed(speed) {
2486 // // Calculate duration based on content width and speed
2487 // const itemWidth = this.items[0].offsetWidth +
2488 // parseInt(window.getComputedStyle(this.items[0]).marginRight) * 2;
2489 // const totalWidth = itemWidth * this.items.length;
2490 // const duration = totalWidth / speed;
2491
2492 // this.content.style.animationDuration = `${duration}s`;
2493 // }
2494 // setSpeed(speed) {
2495 // // Get full content width
2496 // //const contentWidth = this.content.scrollWidth;
2497 // const containerWidth = this.container.offsetWidth;
2498 // this.items = Array.from(this.content.children);
2499 // this.setSlidesPerView();
2500
2501 // // count total items
2502 // const speed_x = this.content.scrollWidth / containerWidth;
2503
2504 // const duration = speed_x * speed;
2505 // console.log(duration / 1000);
2506 // this.content.style.animationDuration = `${duration}ms`;
2507 // }
2508
2509 setSpeed(pixelsPerSecond) {
2510 pixelsPerSecond = (1000 * 250) / pixelsPerSecond;
2511 // Make sure slidesPerView & item widths are up to date.
2512 this.items = Array.from(this.content.children);
2513 this.setSlidesPerView();
2514 this.itemWidth = Number(this.itemWidth);
2515 this.spaceBetween = Number(this.spaceBetween);
2516 this.slidesPerView = Number(this.slidesPerView);
2517 const extraWidth =
2518 (this.itemWidth + this.spaceBetween) * this.slidesPerView;
2519 // Total distance to scroll (the entire marquee width).
2520 const distance = this.content.scrollWidth - extraWidth;
2521 // Duration in seconds = distance(px) / speed(px/sec).
2522 const duration = distance / pixelsPerSecond;
2523 //console.log(distance);
2524 // Apply as seconds to match CSS animation-duration.
2525 this.content.style.animationDuration = `${duration}s`;
2526 this.content.style.setProperty('--sps_extrawidth', `${extraWidth}px`);
2527 }
2528
2529 pause() {
2530 this.content.style.animationPlayState = 'paused';
2531 this.isPaused = true;
2532 }
2533 resume() {
2534 if (this.isPaused) {
2535 this.content.style.animationPlayState = 'running';
2536 this.isPaused = false;
2537 }
2538 }
2539 updateAnimation() {
2540 // Force reflow to restart animation
2541 this.content.style.animation = 'none';
2542 this.content.offsetHeight; // Trigger reflow
2543 this.content.style.animation = '';
2544 // Reapply speed
2545 this.setSpeed(this.options.speed);
2546 }
2547 }
2548
2549 //------------------------title effect js--------
2550 function wrapLines() {
2551 const selector = `
2552 .sp-smart-post-grid-two .sp-smart-post-title,
2553 .sp-smart-post-grid-three .sp-smart-post-title,
2554 .sp-smart-post-carousel-two .sp-smart-post-title,
2555 .sp-smart-post-slider .sp-smart-post-title,
2556 .sp-smart-post-slider-two .sp-smart-post-title,
2557 .sp-smart-post-thumbnail-slider .sp-smart-post-title,
2558 .sp-smart-post-thumbnail-slide-two .sp-smart-post-title,
2559 .sp-smart-post-grid-four .sp-smart-post-title,
2560 .sp-smart-post-grid-five .sp-smart-post-title,
2561 .sp-smart-post-grid-six .sp-smart-post-title,
2562 .sp-smart-post-timeline-two .sp-smart-post-title`;
2563
2564 document.querySelectorAll(selector).forEach((title) => {
2565 if (title.dataset.processed === 'true') return;
2566
2567 const textElement = title.querySelector('.sp-smart-post-title-text');
2568 if (!textElement) return;
2569
2570 // Get trimmed text content only from title-text span
2571 const text = textElement.textContent.trim();
2572 if (!text) return;
2573
2574 const temp = document.createElement('div');
2575 const style = window.getComputedStyle(textElement);
2576 Object.assign(temp.style, {
2577 position: 'absolute',
2578 visibility: 'hidden',
2579 whiteSpace: 'nowrap',
2580 fontSize: style.fontSize,
2581 fontFamily: style.fontFamily,
2582 fontWeight: style.fontWeight,
2583 });
2584 document.body.appendChild(temp);
2585
2586 const containerWidth = textElement.offsetWidth || title.offsetWidth;
2587 const words = text.split(' ');
2588 const lines = [];
2589 let currentLine = '';
2590
2591 words.forEach((word) => {
2592 const testLine = currentLine ? `${currentLine} ${word}` : word;
2593 temp.textContent = testLine;
2594 if (temp.offsetWidth > containerWidth && currentLine) {
2595 lines.push(currentLine);
2596 currentLine = word;
2597 } else {
2598 currentLine = testLine;
2599 }
2600 });
2601 if (currentLine) lines.push(currentLine);
2602 document.body.removeChild(temp);
2603
2604 textElement.innerHTML = ''; // �
2605 clear only text span
2606
2607 lines.forEach((line, index) => {
2608 const span = document.createElement('span');
2609 span.className = 'line';
2610 span.textContent = line;
2611 textElement.appendChild(span);
2612 if (index < lines.length - 1) {
2613 textElement.appendChild(document.createElement('br'));
2614 }
2615 });
2616
2617 title.dataset.processed = 'true';
2618 });
2619 }
2620
2621 document.addEventListener('DOMContentLoaded', () => {
2622 wrapLines();
2623 });
2624
2625 document.addEventListener( 'DOMContentLoaded', () => {
2626 const dividerHideShow = () => {
2627 const containers = document.querySelectorAll(".sp-smart-post-smart-lists-wrapper.sp-smart-post-smart-lists-front-end");
2628 if ( ! containers.length ) return;
2629 containers.forEach( container => {
2630 const horizontalList = container?.classList.contains("sp-list-orientation-horizontal");
2631 if ( ! horizontalList ) return;
2632 const itemsEl = container.querySelectorAll(".wp-block-sp-smart-post-show-smart-list");
2633 let currentTop = null;
2634 let lastInRow = null;
2635 itemsEl.forEach(element => {
2636 element.classList.add("sp-list-divider");
2637 });
2638 itemsEl.forEach(element => {
2639 if ( currentTop === null ) {
2640 currentTop = element.offsetTop;
2641 }
2642 if (element.offsetTop !== currentTop) {
2643 // new row started
2644 if (lastInRow) {
2645 lastInRow.classList.remove("sp-list-divider");
2646 }
2647 currentTop = element.offsetTop;
2648 }
2649 lastInRow = element;
2650 });
2651 if (lastInRow) {
2652 lastInRow.classList.remove("sp-list-divider");
2653 }
2654 })
2655 }
2656 dividerHideShow();
2657 window.addEventListener("resize", dividerHideShow);
2658 })
2659
2660 document.addEventListener( "DOMContentLoaded", () => {
2661 document.querySelectorAll(".sp-smart-post-background-layout .sp-smart-post-card-image video").forEach(video => {
2662 video.controls = false;
2663 video.muted = true;
2664 video.autoplay = true;
2665 video.loop = true;
2666 video.play();
2667 });
2668 })