PluginProbe
Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News / 4.0.8
Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News v4.0.8
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 / builder-scripts.js

builder-scripts.js in Smart Post – Post Grid, Post Carousel, Post Slider Gutenberg Blocks for Blog & News 4.0.8, at blocks/assets/js/builder-scripts.js

3,495 lines 94.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function () {
2 window.addEventListener( 'elementor/frontend/init', function () {
3 elementorFrontend.hooks.addAction(
4 'frontend/element_ready/smart_post_show_pro_template.default',
5 function ( $scope ) {
6 if ( typeof init === 'function' ) {
7 init();
8 }
9 }
10 );
11 } );
12 } )();
13
14 function init() {
15 // Social link copy function.
16 const socialLinkCopy = () => {
17 const socialLinks = document.querySelectorAll(
18 '.sp-smart-post-social-share .sp-smart-post-copy-btn'
19 );
20
21 if (0 < socialLinks.length) {
22 socialLinks.forEach((element) => {
23 element.addEventListener('click', (e) => {
24 e.preventDefault();
25 const link = e.target
26 .closest('.sp-smart-post-copy-btn')
27 .getAttribute('data-url');
28
29 if (link && navigator.clipboard) {
30 navigator.clipboard.writeText(link);
31 } else {
32 console.log(
33 'Clipboard API not supported or link missing.'
34 );
35 }
36 });
37 });
38 }
39 };
40
41 // Post Modal custom lightbox.
42 const modal = () => {
43 const modalContainers = document.querySelectorAll(
44 '.sp-smart-post-modal-container'
45 );
46 let imageSize;
47
48 // Utility: Get next or previous post ID based on current
49 const getNextPostId = (direction, currentId, allIds) => {
50 const index = allIds.indexOf(String(currentId));
51 if (index === -1) {
52 return currentId;
53 }
54 if (direction === 'next') {
55 return allIds[(index + 1) % allIds.length];
56 } else if (direction === 'prev') {
57 return allIds[(index - 1 + allIds.length) % allIds.length];
58 }
59 return currentId;
60 };
61
62 const getReadingTime = (content, attr) => {
63 const text = content.trim();
64 const characterCount = text.length;
65 const wordCount = text.split(/\s+/).filter(Boolean).length;
66 const allCount = {
67 words: wordCount,
68 chars: characterCount,
69 };
70 attr = JSON.parse(attr);
71 const type = attr?.unit;
72 return `${Math.floor(
73 parseInt(allCount[type]) / parseInt(attr.value)
74 )} Min Read`;
75 };
76 const sendPostIdAjax = (postId, modal) => {
77 fetch(sp_smart_post_block_localize.ajaxUrl, {
78 method: 'POST',
79 headers: {
80 'Content-Type': 'application/x-www-form-urlencoded',
81 },
82 body: new URLSearchParams({
83 action: 'sp_handle_post_id',
84 nonce: sp_smart_post_block_localize.ajaxNonce,
85 post_id: postId,
86 image_size: imageSize,
87 }),
88 })
89 .then((res) => res.json())
90 .then((data) => {
91 if (data.success) {
92 const postData = data.data;
93 const modalImage = postData.post_image;
94 const modalTitle = postData.title;
95 const modalAuthor = postData.author_name;
96 const modalDate = postData.date_time;
97 const modalTaxonomy = postData.meta_taxonomy;
98 const modalBadges = postData.post_badges_list;
99 const modalContent = postData.content.replace(
100 /^"|"$/g,
101 ''
102 );
103 const modalComment = postData.post_data.comment_count;
104 const modalViews = postData.post_views;
105 const modalLike = postData.like_button;
106 const readingTimeContainer = modal.querySelector(
107 '.sp-smart-post-reading-time'
108 );
109 const readValue = readingTimeContainer
110 ? readingTimeContainer.dataset.readTime
111 : '';
112 let readTime;
113 if (readValue) {
114 readTime = getReadingTime(
115 modalContent.replace(/<\/?p>/g, ''),
116 readValue
117 );
118 }
119
120 modal.querySelector(
121 '.sp-smart-post-card-modal-image'
122 ).innerHTML = modalImage;
123 modal.querySelector(
124 '.sp-post-modal-title'
125 ).textContent = modalTitle;
126 const authorEl = modal.querySelector('.sp-meta-author');
127 if (authorEl) {
128 authorEl.innerText = modalAuthor;
129 }
130 const dateEl = modal.querySelector('.sp-meta-date');
131 if (dateEl) {
132 dateEl.innerText = modalDate;
133 }
134 const commentsEl =
135 modal.querySelector('.sp-meta-comments');
136 if (commentsEl) {
137 commentsEl.innerText = modalComment;
138 }
139 const viewsEl = modal.querySelector('.sp-meta-views');
140 if (viewsEl) {
141 viewsEl.innerText = modalViews;
142 }
143 const likesEl = modal.querySelector(
144 '.sp-smart-post-likes'
145 );
146 if (likesEl) {
147 likesEl.innerHTML = modalLike;
148 }
149 const readTimeEl = modal.querySelector(
150 '.sp-meta-reading-time'
151 );
152 if (readTimeEl) {
153 readTimeEl.innerHTML = readTime;
154 }
155
156 const postBadgesEl = modal.querySelector(
157 '.sp-title-badges-list'
158 );
159 if (
160 postBadgesEl &&
161 modalBadges &&
162 modalBadges?.length > 0
163 ) {
164 const listItems = modalBadges
165 .map(
166 (item) =>
167 `<li class="sp-title-badge-item">${item.name}</li>`
168 )
169 ?.join('');
170 postBadgesEl.innerHTML = listItems;
171 }
172
173 modal.querySelector(
174 '.sp-post-modal-excerpt'
175 ).innerHTML = modalContent;
176 modal.querySelector(
177 '.sp-meta-taxonomy-category'
178 ).innerText = modalTaxonomy;
179
180 setTimeout(() => {
181 modal.style.opacity = 1;
182 }, 300);
183 } else {
184 console.log('PHP error:', data.data);
185 }
186 })
187 .catch((err) => {
188 console.log('Fetch error:', err);
189 });
190 };
191
192 const cleanData = (modal) => {
193 modal.querySelector('.sp-smart-post-card-modal-image').innerHTML =
194 '';
195 modal.querySelector('.sp-post-modal-title').textContent = '';
196 modal.querySelector('.sp-meta-taxonomy-category').innerText = '';
197 modal.querySelector('.sp-post-modal-excerpt').innerHTML = '';
198 };
199
200 // Open modal and setup navigation
201 const openModal = (e, container) => {
202 e.preventDefault();
203 let modalData;
204
205 const modal = container.querySelector(
206 '.sp-smart-post-modal-container'
207 );
208
209 imageSize = container.querySelector(
210 '.sp-smart-post-modal-container .sp-smart-post-card-modal-image'
211 ).dataset.imageSize;
212
213 const clickedCard = e.target.closest('.sp-smart-post-card');
214 const clickedTitle = clickedCard?.querySelector(
215 '.sp-smart-post-title-wrapper'
216 );
217
218 if (!clickedTitle) {
219 return;
220 }
221
222 let activePostId = String(clickedTitle.dataset.postId); // Initialize properly
223 const allTitles = container.querySelectorAll(
224 '.sp-smart-post-title-wrapper'
225 );
226 const allIds = [
227 ...new Set(
228 Array.from(allTitles).map((item) => item.dataset.postId)
229 ),
230 ];
231 const nextBtn = container.querySelector('.sp-smart-modal-next');
232 const prevBtn = container.querySelector('.sp-smart-modal-prev');
233
234 // sendPostIdAjax( clickedTitle.dataset.postId, modal );
235 // Setup navigation button handlers
236 const setupNav = (btn, direction) => {
237 if (!btn) {
238 return;
239 }
240 btn.onclick = () => {
241 activePostId = getNextPostId(
242 direction,
243 activePostId,
244 allIds
245 );
246 // modal.style.opacity = 0;
247 sendPostIdAjax(activePostId, modal);
248 };
249 };
250
251 setupNav(nextBtn, 'next');
252 setupNav(prevBtn, 'prev');
253
254 // modal.style.display = "block";
255 modal.classList.add('is_open');
256 };
257
258 // Close modal
259 const closeModal = (closeBtn) => {
260 const modal = closeBtn.closest('.sp-smart-post-modal-container');
261 // if ( modal ) modal.style.display = "none";
262
263 modal.style.opacity = 0;
264 if (modal) {
265 modal.classList.remove('is_open');
266 }
267 cleanData(modal);
268 };
269
270 // Attach modal close events
271 modalContainers.forEach((modal) => {
272 const box = modal.querySelector('.sp-smart-post-modal-content');
273 const nextBtn = modal.querySelector('.sp-smart-modal-next');
274 const prevBtn = modal.querySelector('.sp-smart-modal-prev');
275 modal
276 .querySelectorAll('.sp-modal-close-btn.cursor')
277 .forEach((btn) => {
278 btn.addEventListener('click', () => closeModal(btn));
279 });
280 modal.addEventListener('click', (event) => {
281 const clickedEl = event.target;
282 // If click is NOT inside box AND not on next/prev buttons
283 if (
284 !box.contains(clickedEl) &&
285 clickedEl !== nextBtn &&
286 clickedEl !== prevBtn &&
287 (!nextBtn || !nextBtn.contains(clickedEl)) &&
288 (!prevBtn || !prevBtn.contains(clickedEl))
289 ) {
290 closeModal(box);
291 }
292 });
293 });
294
295 // Attach open modal triggers using event delegation for better performance
296 modalContainers.forEach((modal) => {
297 if (!modal.textContent.trim()) {
298 return;
299 }
300
301 const container = modal.parentElement;
302
303 container.addEventListener('click', (e) => {
304 // Do not trigger if the click is inside an already open modal.
305 if (e.target.closest('.sp-smart-post-modal-container')) {
306 return;
307 }
308
309 const trigger = e.target.closest(
310 '.sp-smart-post-card-image a, .sp-smart-post-title-wrapper, .sp-smart-post-read-more-button a'
311 );
312
313 if (!trigger) {
314 return;
315 }
316
317 const clickedCard = trigger.closest('.sp-smart-post-card');
318 if (!clickedCard) {
319 return;
320 }
321
322 const clickedTitle = clickedCard.querySelector(
323 '.sp-smart-post-title-wrapper'
324 );
325 if (!clickedTitle || !clickedTitle.dataset.postId) {
326 return;
327 }
328
329 e.preventDefault();
330 modal.style.opacity = 0;
331 sendPostIdAjax(clickedTitle.dataset.postId, modal);
332 openModal(e, container);
333 });
334 });
335 };
336
337 // Initialize image gallery and video players.
338 const initSmartPostFeatures = (context = document) => {
339 // --- Slider Init ---
340 const sliderWrappers = context.querySelectorAll('.sp-slider-wrapper');
341
342 sliderWrappers.forEach((slideWrapper) => {
343 if (!slideWrapper.classList.contains('sp-slider-initialized')) {
344 new SPSlider(slideWrapper, {
345 loop: true,
346 autoplay: true,
347 interval: 3000,
348 speed: 600,
349 });
350 slideWrapper.classList.add('sp-slider-initialized');
351 }
352 });
353
354 // --- Video Player Init ---
355 const videoPostList = context.querySelectorAll(
356 '.sp-smart-video-player'
357 );
358
359 videoPostList.forEach((videoContainer) => {
360 if (!videoContainer.classList.contains('sp-video-initialized')) {
361 const videoUrl = videoContainer.dataset.video_url;
362 const featureImage = videoContainer.dataset.image_url;
363 const videoCaption = videoContainer.dataset.video_caption;
364
365 new SpSmartVideoPlayer(videoContainer, videoUrl, {
366 featureImage,
367 videoCaption,
368 showFeatureImage: true,
369 transitionSpeed: 500,
370 });
371 videoContainer.classList.add('sp-video-initialized');
372 }
373 videoContainer.addEventListener('click', function (event) {
374 event.preventDefault();
375 event.stopPropagation();
376
377 const featureVideo = videoContainer.querySelector('video');
378 if (featureVideo) {
379 if (featureVideo.paused) {
380 featureVideo.play();
381 } else {
382 featureVideo.pause();
383 }
384 }
385 });
386 });
387 };
388
389 initSmartPostFeatures();
390
391 socialLinkCopy();
392
393 modal();
394 /**
395 * Carousel Pagination Function.
396 * @param paginationClass
397 */
398 const paginationDotNumber = (paginationClass) => {
399 return {
400 el: paginationClass,
401 clickable: true,
402 renderBullet(index, className) {
403 return (
404 '<span class="' + className + '">' + (index + 1) + '</span>'
405 );
406 },
407 };
408 };
409 /**
410 * Helper to safely parse JSON.
411 * @param str
412 * @param fallback
413 */
414 const safeJSONParse = (str, fallback = {}) => {
415 try {
416 return JSON.parse(str) || fallback;
417 } catch (e) {
418 console.log('Failed to parse JSON:', str, e);
419 return fallback;
420 }
421 };
422
423 /**
424 * Initializes Timeline layout with dynamic height adjustment.
425 */
426 document
427 .querySelectorAll('.sp-smart-post-timeline-three')
428 .forEach((containerEl) => {
429 const swiperEl = containerEl.querySelector('.swiper-container');
430 if (!swiperEl) {
431 return;
432 }
433 const getBlockId = containerEl.getAttribute('id') || '';
434 const blockId = getBlockId ? `#${getBlockId}` : '';
435
436 const postContainers = containerEl.querySelectorAll(
437 `${blockId} .sp-smart-post-timeline-three-post-container`
438 );
439 const wrapper = document.querySelector(
440 `${blockId} .timeline-three-layout-two .swiper-wrapper`
441 );
442 const timelineBorder = document.querySelector(
443 `${blockId} .timeline-three-layout-two .sp-smart-post-timeline-border`
444 );
445
446 // Calculate max height from posts.
447 let maxHeight = 0;
448 postContainers.forEach((post) => {
449 maxHeight = Math.max(maxHeight, post.clientHeight);
450 });
451 maxHeight += 60; // Add margin
452
453 // Apply calculated height
454 if (wrapper) {
455 wrapper.style.marginTop = `${maxHeight}px`;
456 }
457 if (timelineBorder) {
458 timelineBorder.style.marginTop = `${maxHeight}px`;
459 }
460
461 const options = safeJSONParse(
462 swiperEl.getAttribute('data-swiper-options')
463 );
464 if (!Object.keys(options).length) {
465 return;
466 }
467
468 new PCPSwiper(swiperEl, options);
469 });
470
471 /**
472 * Initializes thumbnail sliders (layout one).
473 */
474 document
475 .querySelectorAll(
476 '.sp-smart-post-thumbnail-slider.sp-smart-post-block-wrapper'
477 )
478 .forEach((containerEl) => {
479 const [mainSwiperEl, thumbsSwiperEl] =
480 containerEl.querySelectorAll('.swiper-container');
481 const thumbsOptions = safeJSONParse(
482 containerEl
483 .querySelector('.sp-smart-post-swiper2')
484 ?.getAttribute('data-swiper-options')?.replace(/u0022/g, '"')
485 );
486 const mainOptions = safeJSONParse(
487 mainSwiperEl?.getAttribute('data-swiper-options')?.replace(/u0022/g, '"')
488 );
489
490 if (
491 !mainSwiperEl ||
492 !thumbsSwiperEl ||
493 !Object.keys(mainOptions).length
494 ) {
495 return;
496 }
497
498 const thumbsSwiper = new PCPSwiper(thumbsSwiperEl, thumbsOptions);
499 const carouselPagination = mainOptions?.pagination;
500 const paginationClassName = carouselPagination?.el;
501 const numberPagination = mainOptions?.pagination?.paginationType
502 ? true
503 : false;
504 const mainSwiper = new PCPSwiper(mainSwiperEl, {
505 ...mainOptions,
506 thumbs: { swiper: thumbsSwiper },
507 pagination: !numberPagination
508 ? carouselPagination
509 : paginationDotNumber(paginationClassName),
510 });
511 });
512
513 /**
514 * Initializes thumbnail sliders (layout two).
515 */
516 document
517 .querySelectorAll(
518 '.sp-smart-post-block-wrapper.sp-smart-thumbnail-slider-two'
519 )
520 .forEach((containerEl) => {
521 const [mainSwiperEl, thumbsSwiperEl] =
522 containerEl.querySelectorAll('.swiper-container');
523 const thumbsOptions = safeJSONParse(
524 containerEl
525 .querySelector('.sp-smart-post-swiper2')
526 ?.getAttribute('data-swiper-options')?.replace(/u0022/g, '"')
527 );
528 const mainOptions = safeJSONParse(
529 mainSwiperEl?.getAttribute('data-swiper-options')?.replace(/u0022/g, '"')
530 );
531
532 if (
533 !mainSwiperEl ||
534 !thumbsSwiperEl ||
535 !Object.keys(mainOptions).length
536 ) {
537 return;
538 }
539
540 const thumbsSwiper = new PCPSwiper(thumbsSwiperEl, thumbsOptions);
541 const mainSwiper = new PCPSwiper(mainSwiperEl, {
542 ...mainOptions,
543 thumbs: { swiper: thumbsSwiper },
544 });
545 });
546
547 /**
548 * Throttles a function using requestAnimationFrame for performance.
549 * @param {Function} callback The function to throttle.
550 * @return {Function} The throttled function.
551 */
552 const throttle = (callback) => {
553 let ticking = false;
554 return (...args) => {
555 if (!ticking) {
556 window.requestAnimationFrame(() => {
557 callback(...args);
558 ticking = false;
559 });
560 ticking = true;
561 }
562 };
563 };
564
565 /**
566 * Initializes and manages scroll-based animations for all timeline blocks on the page.
567 * Caches DOM elements and uses a single throttled scroll listener for better performance.
568 */
569 const initTimelineScroll = () => {
570 const allTimelinesData = [];
571 document
572 .querySelectorAll('.sp-smart-post-show-scroll-wrapper')
573 .forEach((timelineWrapper) => {
574 const blockId = timelineWrapper.id;
575 if (!blockId) {
576 return;
577 }
578
579 const postCards = timelineWrapper.querySelectorAll(
580 '.sp-smart-post-timeline-one-post-container .sp-smart-indicator-circle'
581 );
582 const indicatorArrows = timelineWrapper.querySelectorAll(
583 '.sp-smart-post-timeline-one-post-container .sp-smart-indicator-arrow'
584 );
585 const timelineContainer = timelineWrapper.querySelector(
586 '.sp-smart-post-timeline-container'
587 );
588
589 // Pre-create or find the style tag for this instance to avoid creating it on every scroll.
590 let styleTag = timelineWrapper.querySelector(
591 'style.sps-dynamic-styles'
592 );
593 if (!styleTag) {
594 styleTag = document.createElement('style');
595 styleTag.className = 'sps-dynamic-styles';
596 timelineWrapper.insertBefore(
597 styleTag,
598 timelineWrapper.firstChild
599 );
600 }
601
602 if (timelineContainer) {
603 allTimelinesData.push({
604 wrapper: timelineWrapper,
605 blockId,
606 postCards,
607 indicatorArrows,
608 timelineContainer,
609 styleTag,
610 });
611 }
612 });
613
614 // 2. Skip if no timelines are found
615 if (allTimelinesData.length === 0) {
616 return;
617 }
618
619 // 3. Create a single, efficient scroll handler for all timelines
620 const handleTimelineScrolls = () => {
621 const scrollY = window.scrollY;
622 const pageHeight = Math.max(document.body.offsetHeight);
623
624 allTimelinesData.forEach((data) => {
625 const wrapperRect = data.wrapper.getBoundingClientRect();
626
627 // Only process timelines currently visible in the viewport (with a buffer)
628 if (
629 wrapperRect.bottom >= 0 &&
630 wrapperRect.top <= window.innerHeight
631 ) {
632 const scrollPosition = scrollY + 200;
633
634 // Indicator activation logic
635 const indicatorActive = (elements) => {
636 elements.forEach((el) => {
637 const elTop =
638 el.getBoundingClientRect().top + scrollY;
639 if (
640 scrollY + 505 > elTop ||
641 scrollPosition + 700 >= pageHeight
642 ) {
643 el.classList.add('active');
644 } else {
645 el.classList.remove('active');
646 }
647 });
648 };
649 indicatorActive(data.postCards);
650 indicatorActive(data.indicatorArrows);
651
652 // Timeline line height calculation logic
653 const timelineRect =
654 data.timelineContainer.getBoundingClientRect();
655 const timelineTop = timelineRect.top + scrollY;
656 let height = 320;
657 let heightUnit = 'px';
658
659 if (
660 timelineRect.bottom - 518 < 0 ||
661 scrollPosition + 700 >= pageHeight
662 ) {
663 height = 100;
664 heightUnit = '%';
665 } else if (
666 scrollPosition > timelineTop &&
667 timelineRect.bottom - 518 > 0
668 ) {
669 height = scrollPosition + 320 - timelineTop;
670 }
671 // Update the dedicated style tag's content, which is more performant than creating new tags.
672 const styleContent = `#${data.blockId} .sp-smart-post-timeline-container::after { height: ${height}${heightUnit} !important; }`;
673 if (data.styleTag.textContent !== styleContent) {
674 data.styleTag.textContent = styleContent;
675 }
676 }
677 });
678 };
679
680 // 4. Add the single throttled listener
681 window.addEventListener('scroll', throttle(handleTimelineScrolls));
682 };
683
684 // Initialize the timeline scroll animations.
685 initTimelineScroll();
686 function updatePaginationData(section, data) {
687 if (!section || !data) {
688 return;
689 }
690 // Update dataset attributes.
691 section.dataset.pages = data.total_pages;
692 section.dataset.current = data.current_page;
693 if (section?.dataset.pages < 2) {
694 section.style.display = 'none';
695 } else {
696 section.style.display = 'flex';
697 }
698 if (section?.dataset.pages < 2) {
699 return false;
700 }
701 updatePaginationUI(section);
702 createPaginationSection(section);
703 }
704 const createPaginationSection = (section) => {
705 const totalPages = parseInt(section.dataset.pages, 10);
706 const currentPage = parseInt(section.dataset.current, 10);
707 const paginationtype = section?.dataset.paginationtype;
708 if (paginationtype == 'pagination') {
709 if (!totalPages || totalPages < 2) {
710 return;
711 }
712
713 const paginationContainer = section.querySelector(
714 '.sp-smart-post-pagination-buttons'
715 );
716 if (!paginationContainer) {
717 return;
718 }
719
720 paginationContainer.innerHTML = ''; // Clear existing buttons
721
722 // Previous Button
723 const prevBtn = document.createElement('a');
724 prevBtn.href = '#';
725 prevBtn.className = 'page-numbers prev';
726 if (currentPage === 1) {
727 prevBtn.classList.add('disabled');
728 }
729 prevBtn.innerHTML = `<i class="sp-icon-left-open"></i> Previous`;
730 paginationContainer.appendChild(prevBtn);
731
732 // Number Buttons
733 for (let i = 1; i <= totalPages; i++) {
734 const pageBtn = document.createElement('a');
735 pageBtn.href = '#';
736 pageBtn.className = 'page-numbers';
737 pageBtn.dataset.page = i;
738 pageBtn.textContent = i;
739
740 if (i === currentPage) {
741 pageBtn.classList.add('current');
742 }
743
744 paginationContainer.appendChild(pageBtn);
745 }
746
747 // Next Button
748 const nextBtn = document.createElement('a');
749 nextBtn.href = '#';
750 nextBtn.className = 'page-numbers next';
751 if (currentPage === totalPages) {
752 nextBtn.classList.add('disabled');
753 }
754 nextBtn.innerHTML = `Next <i class="sp-icon-right-open"></i>`;
755 paginationContainer.appendChild(nextBtn);
756 }
757 };
758
759 const updatePaginationUI = (section) => {
760 const current = parseInt(section.dataset.current, 10);
761 const total = parseInt(section.dataset.pages, 10);
762 const paginationtype = section?.dataset.paginationtype;
763
764 section.querySelectorAll('.page-numbers[data-page]').forEach((link) => {
765 const page = parseInt(link.dataset.page, 10);
766 link.classList.toggle('current', page === current);
767 });
768 const prev = section.querySelector('.page-numbers.prev');
769 const next = section.querySelector('.page-numbers.next');
770 const loadMoreWrapper = section.querySelector(
771 '.sp-smart-post-load-more-button'
772 );
773 if (prev) {
774 prev.classList.toggle('disabled', current <= 1);
775 }
776 if (next) {
777 next.classList.toggle('disabled', current >= total);
778 }
779 // For nav-arrow buttons.
780 const [prevNav, nextNav] = section.querySelectorAll(
781 '.sp-smart-post-grid-nav-arrow-btn'
782 );
783 if (prevNav && nextNav) {
784 prevNav.style.pointerEvents = current <= 1 ? 'none' : 'auto';
785 prevNav.style.opacity = current <= 1 ? '0.5' : '1';
786 nextNav.style.pointerEvents = current >= total ? 'none' : 'auto';
787 nextNav.style.opacity = current >= total ? '0.5' : '1';
788 }
789 if (loadMoreWrapper) {
790 if (current >= total) {
791 const loadMoreWrapperBtn = loadMoreWrapper.querySelector('a');
792 if (loadMoreWrapperBtn) {
793 loadMoreWrapperBtn.style.display = 'none';
794 }
795 } else {
796 loadMoreWrapper.style.display = 'block';
797 }
798 }
799 };
800 function hasFilterCounts(count_data) {
801 if (!count_data || typeof count_data !== 'object') {
802 return false;
803 }
804
805 return Object.entries(count_data).some(([key, value]) => {
806 // Skip pagination
807 if (key === 'pagination') {
808 return false;
809 }
810 // Check if it's a non-empty array
811 return Array.isArray(value) && value.length > 0;
812 });
813 }
814 function getBlockParams() {
815 const url = new URL(window.location.href);
816 const params = new URLSearchParams(url.search);
817 const blockParams = {};
818 for (const [key, value] of params.entries()) {
819 if (
820 key === 'block' ||
821 key.startsWith('tx_') ||
822 key.startsWith('sps_')
823 ) {
824 blockParams[key] = value;
825 }
826 }
827 return blockParams;
828 }
829
830 function updateSelectByParams(params, blockParent) {
831 // Loop through each param and try to update the matching <select>
832 for (const key in params) {
833 if ('sps_page' === key) {
834 const value = params[key];
835 const pagination_section = blockParent.querySelector(
836 '.sp-smart-post-pagination-section'
837 );
838 pagination_section.dataset.current = value;
839 } else if ('sps_search' === key) {
840 const search_input = blockParent.querySelector(
841 `input[name="${key}"]`
842 );
843 const value = params[key];
844 search_input.value = value;
845 } else if ('sps_date' === key) {
846 const selectDate = blockParent.querySelector(
847 '.sp-smart-post-show-date-filter-date-value'
848 );
849 const value = params[key];
850 const formatted = value
851 .replace(/^from-/, '')
852 .replace(/-to-/, ' - ');
853
854 // selectDate.innerText = formatted;
855 if (selectDate) {
856 selectDate.innerText = formatted;
857 }
858 } else if ('sps_preset_date' === key) {
859 const slectorData = blockParent.querySelector(
860 '.sp-smart-post-live-filter-dropdown-date, .sp-smart-post-live-filter-button-date'
861 );
862
863 const presetType = slectorData.dataset.presetType;
864 const labels = {
865 today: 'Today',
866 yesterday: 'Yesterday',
867 lastSevenDays: 'Last 7 Days',
868 lastThirtyDays: 'Last 30 Days',
869 thisWeek: 'This Week',
870 lastWeek: 'Last Week',
871 thisMonth: 'This Month',
872 lastMonth: 'Last Month',
873 thisYear: 'This Year',
874 };
875 const value = params[key];
876 const label = labels[value];
877
878 if (presetType === 'dropdown') {
879 const selectDate = blockParent.querySelector(
880 '.sp-smart-post-show-date-filter-date-value'
881 );
882 selectDate.innerText = label;
883 }
884
885 const allLinks = slectorData.querySelectorAll('li > a');
886 allLinks.forEach((link) => {
887 if (link.textContent.trim() === label) {
888 link.classList.add('active');
889 } else {
890 // Remove active from other links if needed
891 link.classList.remove('active');
892 }
893 });
894 } else {
895 const select = blockParent.querySelector(
896 `select[name="${key}"]`
897 );
898 if (select) {
899 const value = params[key];
900 // Set value if option exists.
901 const option = select.querySelector(
902 `option[value="${value}"]`
903 );
904 if (option) {
905 option.disabled = false;
906 option.selected = true;
907 select.value = value;
908 // Optional: trigger change event
909 // select.dispatchEvent(new Event('change'));
910 }
911 }
912 }
913 }
914 }
915
916 /**
917 * Groups slides into swiper-slide wrappers when using specific effects
918 * @param {HTMLElement} containerEl - The swiper container element
919 * @param {Object} sps_swiper_option - Swiper configuration options
920 * @param {string[]} [effectsRequiringWrapping=['fade', 'cube', 'flip']] - Effects that need slide wrapping
921 */
922 function wrapSlidesForEffect(
923 containerEl,
924 sps_swiper_option,
925 effectsRequiringWrapping = ['fade', 'cube', 'flip']
926 ) {
927 // Check if we need to wrap slides
928 if (
929 sps_swiper_option?.effect &&
930 effectsRequiringWrapping.includes(sps_swiper_option.effect)
931 ) {
932 // Get valid slidesPerView (minimum 1)
933 const slidesPerView = Math.max(
934 1,
935 parseInt(sps_swiper_option?.slidesPerView) || 1
936 );
937 // Only proceed if we need multiple slides per view
938 if (slidesPerView > 1) {
939 const swiperWrapper =
940 containerEl.querySelector('.swiper-wrapper');
941 const loopItems =
942 containerEl.querySelectorAll('.sp-slide-item');
943 loopItems.forEach((item) => {
944 item.classList.remove('swiper-slide');
945 });
946 if (swiperWrapper && loopItems.length > 0) {
947 // Create document fragment for better performance
948 const fragment = document.createDocumentFragment();
949 // Group slides into chunks
950 for (let i = 0; i < loopItems.length; i += slidesPerView) {
951 const slideWrapper = document.createElement('div');
952 slideWrapper.className = 'swiper-slide';
953
954 // Add slides to this group
955 const endIndex = Math.min(
956 i + slidesPerView,
957 loopItems.length
958 );
959 for (let j = i; j < endIndex; j++) {
960 slideWrapper.appendChild(
961 loopItems[j].cloneNode(true)
962 );
963 }
964 fragment.appendChild(slideWrapper);
965 }
966
967 // Clear existing content and add new grouped slides
968 swiperWrapper.innerHTML = '';
969 swiperWrapper.appendChild(fragment);
970 }
971 }
972 }
973 }
974 const selected_params = getBlockParams();
975 // Loop over each Smart Post block container.
976
977 document.querySelectorAll('.sp-smart-post-wrapper').forEach((container) => {
978 /**
979 * Preloader.
980 * Fades out preloader.
981 */
982 const preloader = container?.querySelector(
983 '.sp-smart-post-show-pro-pre-query .sp-smart-post-preloader'
984 );
985
986 if (preloader) {
987 // Wait 2.5 seconds before showing fallback.
988 setTimeout(() => {
989 preloader.classList.remove('sp-d-block');
990 preloader.classList.add('sp-d-hidden');
991 container.classList.add('sp-smart-preloader-removed');
992 }, 500);
993 }
994 // Social Links Copy notification.
995 const socialCopyLinks = container.querySelectorAll(
996 '.sp-copy-url-area .sp-smart-post-copy-btn'
997 );
998 if (socialCopyLinks.length > 0) {
999 let classText;
1000 socialCopyLinks.forEach((socialCopyLink) => {
1001 socialCopyLink.addEventListener('click', (e) => {
1002 const copyText = socialCopyLink.querySelector(
1003 '.sp-post-url-copy-popup'
1004 );
1005 // e.preventDefault();
1006 copyText.classList.remove('sp-d-hidden');
1007 copyText.classList.add('sp-d-block');
1008 clearTimeout(classText);
1009 classText = setTimeout(() => {
1010 copyText.classList.remove('sp-d-block');
1011 copyText.classList.add('sp-d-hidden');
1012 }, 2000);
1013 });
1014 });
1015 }
1016 if (
1017 container?.classList.contains('sp-smart-post-thumbnail-slider') ||
1018 container?.classList.contains(
1019 'sp-smart-post-thumbnail-slider-two'
1020 ) ||
1021 container?.classList.contains('sp-smart-thumbnail-slider-two')
1022 ) {
1023 return false;
1024 }
1025
1026 // Closest parent block with specific block class.
1027 const blockParent = container.closest(
1028 '.wp-block-sp-smart-post-show-smart-post-parent'
1029 );
1030
1031 const blockId = container.dataset.blockid;
1032 const page_id = container.dataset.pageid || '';
1033 const block_location = container.dataset.location || '';
1034
1035 if (blockParent && selected_params?.block === blockId) {
1036 updateSelectByParams(selected_params, blockParent);
1037 }
1038 /**
1039 * SECTION 1: INIT SWIPER OR MARQUEE SLIDERS
1040 */
1041 const containerEl = container.querySelector(
1042 '.layout-standard, .layout-center, .swiper-container, .sp_marquee-container'
1043 );
1044
1045 let sps_swiper = null;
1046 let sps_swiper_option = {};
1047 let sps_marquee = null;
1048 let sps_marquee_option = {};
1049
1050 // Initialize Swiper slider if present
1051 if (containerEl?.classList.contains('swiper-container')) {
1052 const options = safeJSONParse(
1053 containerEl.getAttribute('data-swiper-options')?.replace(/u0022/g, '"')
1054 );
1055
1056 if (!Object.keys(options).length) {
1057 return;
1058 }
1059 const paginationOptions = options?.pagination;
1060 const paginationClassName = paginationOptions?.el;
1061 const isNumberPagination = !!options.pagination?.paginationType;
1062 sps_swiper_option = {
1063 ...options,
1064 pagination: isNumberPagination
1065 ? paginationDotNumber(paginationClassName)
1066 : paginationOptions,
1067 };
1068
1069 if (
1070 sps_swiper_option.effect &&
1071 ['fade', 'cube', 'flip'].includes(sps_swiper_option?.effect)
1072 ) {
1073 wrapSlidesForEffect(containerEl, sps_swiper_option);
1074 }
1075
1076 sps_swiper = new PCPSwiper(containerEl, sps_swiper_option);
1077 }
1078
1079 // Initialize Marquee slider if present
1080 if (containerEl?.classList.contains('sp_marquee-container')) {
1081 sps_marquee_option = safeJSONParse(
1082 containerEl.getAttribute('data-options')
1083 );
1084 if (!Object.keys(sps_marquee_option).length) {
1085 return;
1086 }
1087 sps_marquee_option = {
1088 speed: sps_marquee_option.speed || 500,
1089 direction:
1090 sps_marquee_option.direction === 'left_to_right'
1091 ? 'right'
1092 : 'left',
1093 pauseOnHover: sps_marquee_option.pauseOnHover !== false,
1094 slidesPerView: sps_marquee_option.slidesPerView || 1,
1095 slidesPerViewMobile:
1096 sps_marquee_option.slidesPerViewMobile || 1,
1097 slidesPerViewTablet:
1098 sps_marquee_option.slidesPerViewTablet || 1,
1099 spaceBetween: sps_marquee_option.spaceBetween || 0,
1100 spaceBetweenMobile: sps_marquee_option.spaceBetweenMobile || 0,
1101 spaceBetweenTablet: sps_marquee_option.spaceBetweenTablet || 0,
1102 };
1103 sps_marquee = new SP_Marquee(containerEl, sps_marquee_option);
1104 }
1105
1106 /**
1107 * SECTION 2: FILTER HANDLING
1108 */
1109
1110 // let sps_date = selected_params['sps_date']
1111 // ? selected_params['sps_date']
1112 // : '';
1113 // let sps_preset_date = selected_params['sps_preset_date']
1114 // ? selected_params['sps_preset_date']
1115 // : '';
1116 let sps_date;
1117 let sps_preset_date;
1118
1119 const filter_fields =
1120 blockParent?.querySelectorAll(
1121 '.sp-smart-post-live-filter-parent select[name], .sp-smart-post-live-filter-parent input[name]'
1122 ) || [];
1123
1124 const filter_relation =
1125 blockParent?.querySelector('.sp-smart-post-live-filter-parent')
1126 ?.dataset.relation || 'and';
1127
1128 // Fetch filtered content.
1129 const loadQueryData = (state) => {
1130 state.block_location = block_location;
1131
1132 return fetch(sp_smart_post_block_localize.restUrl, {
1133 method: 'POST',
1134 headers: { 'Content-Type': 'application/json' },
1135 body: JSON.stringify(state),
1136 }).then((res) => res.json());
1137 };
1138
1139 function updateURLQuery(customParams) {
1140 const baseUrl = new URL(
1141 sp_smart_post_block_localize.permalink_structure ||
1142 window.location.href
1143 );
1144
1145 // START FROM EXISTING URL PARAMS
1146 const params = new URLSearchParams(baseUrl.search);
1147
1148 const allowedKeys = [
1149 'page_id',
1150 'block',
1151 'tx_category',
1152 'sps_date',
1153 'sps_preset_date',
1154 'sps_sort',
1155 'sps_search',
1156 'sps_page',
1157 ];
1158
1159 // Always ensure block
1160 if (blockId) {
1161 params.set('block', blockId);
1162 }
1163
1164 Object.entries(customParams).forEach(([key, value]) => {
1165 if (!allowedKeys.includes(key)) return;
1166
1167 if (value !== '' && value !== null && value !== undefined) {
1168 params.set(key, value);
1169 } else {
1170 params.delete(key);
1171 }
1172 });
1173
1174 // Cleanup unknown params
1175 [...params.keys()].forEach((key) => {
1176 if (!allowedKeys.includes(key)) {
1177 params.delete(key);
1178 }
1179 });
1180
1181 baseUrl.search = params.toString();
1182 window.history.replaceState({}, '', baseUrl.toString());
1183 }
1184
1185 // Build filter state object.
1186 function getFilterState(
1187 event,
1188 update_url = true,
1189 pagination_section = null
1190 ) {
1191 const state = {};
1192 const currentField = event?.target || null;
1193 pagination_section =
1194 pagination_section ??
1195 currentField?.closest('.sp-smart-post-pagination-section');
1196 if (
1197 pagination_section &&
1198 'pagination' == pagination_section?.dataset.paginationtype &&
1199 currentField.tagName === 'A'
1200 ) {
1201 const currentPage = parseInt(
1202 pagination_section.dataset.current,
1203 10
1204 );
1205 newPage = 1;
1206 if (currentField.classList.contains('prev')) {
1207 newPage = currentPage - 1;
1208 } else if (currentField.classList.contains('next')) {
1209 newPage = currentPage + 1;
1210 } else {
1211 newPage = parseInt(currentField.dataset.page, 10);
1212 }
1213 if (newPage > 1) {
1214 state.sps_page = newPage || 1;
1215 }
1216 }
1217 filter_fields.forEach((field) => {
1218 // if (pagination_section && field.tagName === 'DIV') {
1219 // if (field.dataset.current > 1) {
1220 // state['page'] = field.dataset.current || 1;
1221 // }
1222 // } else {
1223
1224 let key = field.name;
1225 const value = field.value.trim();
1226 if (key === 'blog_search') {
1227 key = 'search';
1228 }
1229
1230 state['sps_preset_date'] = sps_preset_date
1231 ? sps_preset_date
1232 : '';
1233 state['sps_date'] = sps_date ? sps_date : '';
1234 state[key] = value;
1235
1236 // }
1237 });
1238
1239 if (update_url) {
1240 updateURLQuery(state);
1241 }
1242 if (Object.keys(state).length > 0) {
1243 state.block = blockId;
1244 state.page_id = page_id;
1245 state.relation = filter_relation;
1246 }
1247
1248 return state;
1249 }
1250
1251 // Initial count load
1252 let currentState = getFilterState(null, false);
1253
1254 function debounce(fn, delay) {
1255 let timer;
1256 return function (...args) {
1257 clearTimeout(timer);
1258 timer = setTimeout(() => fn.apply(this, args), delay);
1259 };
1260 }
1261 const pagination_section = container.querySelector(
1262 '.sp-smart-post-pagination-section'
1263 );
1264 // Load More
1265 const loadMoreWrapper = pagination_section?.querySelector(
1266 '.sp-smart-post-load-more-button'
1267 );
1268
1269 let fromDate;
1270 let toDate;
1271 function datePicker() {
1272 blockParent
1273 .querySelectorAll('[sps-date-filter-calendar]')
1274 .forEach((calendar) => {
1275 const currentDate = new Date();
1276 const defaultDate = selected_params['sps_date'];
1277
1278 // If sps_date exists, make the date as default.
1279 if (defaultDate && defaultDate.startsWith('from-')) {
1280 const match = defaultDate.match(
1281 /from-(\d{2})-(\d{2})-(\d{4})-to-(\d{2})-(\d{2})-(\d{4})/
1282 );
1283
1284 if (match) {
1285 fromDate = new Date(
1286 match[3],
1287 match[2] - 1,
1288 match[1]
1289 );
1290 toDate = new Date(match[6], match[5] - 1, match[4]);
1291 }
1292 }
1293
1294 const monthNames = [
1295 'January',
1296 'February',
1297 'March',
1298 'April',
1299 'May',
1300 'June',
1301 'July',
1302 'August',
1303 'September',
1304 'October',
1305 'November',
1306 'December',
1307 ];
1308
1309 const grid = calendar.querySelector(
1310 '.sps-smart-post-date-filter-calendar-grid'
1311 );
1312 const monthLabel = calendar.querySelector(
1313 '.sps-date-filter-month-label'
1314 );
1315 const yearLabel = calendar.querySelector(
1316 '.sps-date-filter-year-label'
1317 );
1318 const monthList = calendar.querySelector(
1319 '.sps-date-filter-month-list'
1320 );
1321
1322 const yearList = calendar.querySelector(
1323 '.sps-date-filter-year-list'
1324 );
1325
1326 if (!grid || !monthLabel || !yearLabel) return;
1327
1328 // ---------- Helpers ----------
1329 const daysInMonth = (date) =>
1330 new Date(
1331 date.getFullYear(),
1332 date.getMonth() + 1,
1333 0
1334 ).getDate();
1335
1336 const firstDayOfMonth = (date) =>
1337 new Date(
1338 date.getFullYear(),
1339 date.getMonth(),
1340 1
1341 ).getDay();
1342
1343 const formatDate = (date) => {
1344 if (!date) return '';
1345 return `${String(date.getDate()).padStart(2, '0')}-${String(
1346 date.getMonth() + 1
1347 ).padStart(2, '0')}-${date.getFullYear()}`;
1348 };
1349
1350 const sameDate = (d1, d2) =>
1351 d1 &&
1352 d2 &&
1353 d1.getDate() === d2.getDate() &&
1354 d1.getMonth() === d2.getMonth() &&
1355 d1.getFullYear() === d2.getFullYear();
1356
1357 const inRange = (date) =>
1358 fromDate && toDate && date > fromDate && date < toDate;
1359
1360 // ---------- Render ----------
1361 function renderCalendar() {
1362 grid.innerHTML = '';
1363 monthLabel.textContent =
1364 monthNames[currentDate.getMonth()];
1365 yearLabel.textContent = currentDate.getFullYear();
1366
1367 const days = [];
1368 const prevMonthDays = new Date(
1369 currentDate.getFullYear(),
1370 currentDate.getMonth(),
1371 0
1372 ).getDate();
1373
1374 const firstDay = firstDayOfMonth(currentDate);
1375
1376 for (
1377 let i = prevMonthDays - firstDay + 1;
1378 i <= prevMonthDays;
1379 i++
1380 ) {
1381 days.push({ day: i, current: false });
1382 }
1383
1384 for (let i = 1; i <= daysInMonth(currentDate); i++) {
1385 days.push({ day: i, current: true });
1386 }
1387
1388 let nextMonthDay = 1;
1389 while (days.length < 42) {
1390 days.push({
1391 day: nextMonthDay++,
1392 current: false,
1393 });
1394 }
1395
1396 days.forEach((d) => {
1397 const btn = document.createElement('button');
1398 const dateObj = new Date(
1399 currentDate.getFullYear(),
1400 currentDate.getMonth(),
1401 d.day
1402 );
1403
1404 btn.textContent = d.day;
1405 btn.className =
1406 'sps-smart-post-date-filter-calendar-day ' +
1407 (d.current ? 'current-month' : 'other-month');
1408
1409 if (sameDate(dateObj, fromDate))
1410 btn.classList.add('from');
1411 if (sameDate(dateObj, toDate))
1412 btn.classList.add('to');
1413 if (inRange(dateObj)) btn.classList.add('in-range');
1414
1415 btn.addEventListener('click', (e) => {
1416 if (!d.current) return;
1417 e.stopPropagation();
1418
1419 monthList.style.display = 'none';
1420 yearList.style.display = 'none';
1421
1422 if (fromDate && toDate) {
1423 fromDate = dateObj;
1424 toDate = null;
1425 renderCalendar();
1426 return;
1427 }
1428
1429 if (!fromDate) {
1430 fromDate = dateObj;
1431 renderCalendar();
1432 return;
1433 }
1434
1435 if (!toDate) {
1436 if (dateObj < fromDate) {
1437 toDate = fromDate;
1438 fromDate = dateObj;
1439 } else {
1440 toDate = dateObj;
1441 }
1442
1443 renderCalendar();
1444 closeDropdown();
1445 }
1446 });
1447
1448 grid.appendChild(btn);
1449 });
1450 }
1451
1452 //---------- ui ----------
1453 function closeDropdown() {
1454 const dropdown = calendar.closest(
1455 '.sp-smart-post-live-filter-dropdown-date'
1456 );
1457 const button = calendar
1458 .closest('.sp-smart-post-date-filter')
1459 ?.querySelector('.sp-smart-post-live-filter-btn');
1460
1461 if (dropdown) dropdown.classList.remove('open');
1462
1463 if (button) {
1464 button.setAttribute('aria-expanded', 'false');
1465 const span = button.querySelector(
1466 '.sp-smart-post-show-date-filter-date-value'
1467 );
1468 if (span) {
1469 span.textContent =
1470 formatDate(fromDate) +
1471 ' - ' +
1472 formatDate(toDate);
1473 sps_date =
1474 'from-' +
1475 formatDate(fromDate) +
1476 '-to-' +
1477 formatDate(toDate);
1478
1479 currentState['sps_date'] =
1480 'from-' +
1481 formatDate(fromDate) +
1482 '-to-' +
1483 formatDate(toDate);
1484
1485 updateURLQuery(currentState);
1486 if (preloader) {
1487 // Wait 2.5 seconds before showing fallback
1488 preloader.classList.add('sp-d-block');
1489 container.classList.remove(
1490 'sp-smart-preloader-removed'
1491 );
1492 }
1493 let itemsEl = null;
1494 delete currentState.html;
1495 loadQueryData(currentState).then((data) => {
1496 if (!data.success) {
1497 return;
1498 }
1499
1500 if (pagination_section) {
1501 pagination_section.dataset.current = 1;
1502 }
1503 if (
1504 sps_marquee &&
1505 containerEl.classList.contains(
1506 'sp_marquee-container'
1507 )
1508 ) {
1509 container.querySelector(
1510 '.sp_marquee-content'
1511 ).innerHTML = data.html;
1512 sps_marquee = new SP_Marquee(
1513 containerEl,
1514 sps_marquee_option
1515 );
1516 } else if (sps_swiper) {
1517 // if (sps_swiper.slides && sps_swiper.slides.length > 0) {
1518 // // sps_swiper.removeAllSlides();
1519 // }
1520
1521 sps_swiper.destroy(true);
1522
1523 container.querySelector(
1524 '.swiper-wrapper'
1525 ).innerHTML = data.html;
1526 if (
1527 sps_swiper_option.effect &&
1528 ['fade', 'cube', 'flip'].includes(
1529 sps_swiper_option?.effect
1530 )
1531 ) {
1532 wrapSlidesForEffect(
1533 containerEl,
1534 sps_swiper_option
1535 );
1536 }
1537 sps_swiper = new PCPSwiper(
1538 containerEl,
1539 sps_swiper_option
1540 );
1541 } else {
1542 const items = container.querySelector(
1543 '.sp-smart-post-items'
1544 );
1545 items.innerHTML = data.html;
1546 itemsEl = items;
1547 // container.querySelector(
1548 // '.sp-smart-post-items'
1549 // ).style.opacity = '1';
1550 // Staggered Animation
1551 if (itemsEl) {
1552 const childItems =
1553 itemsEl.querySelectorAll(
1554 '.sp-smart-post-card, .swiper-slide'
1555 );
1556 childItems.forEach((el, index) => {
1557 el.style.opacity = 0;
1558 el.style.animation = `fadeIn 0.7s ease-in-out forwards`;
1559 el.style.animationDelay = `${index * 0.1}s`; // 80ms stagger
1560 });
1561 }
1562 }
1563 const termCounts = data.count_data;
1564 if (
1565 termCounts &&
1566 hasFilterCounts(termCounts)
1567 ) {
1568 updateFilterCounts(
1569 termCounts,
1570 pagination_section,
1571 blockParent
1572 );
1573 updateAllSmartPostFilterCounts(
1574 blockParent
1575 );
1576 } else if (
1577 Object.keys(currentState).length > 0
1578 ) {
1579 blockParent
1580 .querySelectorAll(
1581 '.sp-smart-post-taxonomy-filter select option'
1582 )
1583 .forEach((option) => {
1584 const slug = option.value;
1585 if (slug) {
1586 option.textContent = `${capitalize(
1587 slug
1588 )} (0)`;
1589 option.disabled = true;
1590 }
1591 });
1592 Object.entries(termCounts).forEach(
1593 ([taxonomy, terms]) => {
1594 // Handle pagination update
1595 if (
1596 taxonomy === 'pagination' &&
1597 pagination_section
1598 ) {
1599 updatePaginationData(
1600 pagination_section,
1601 terms
1602 );
1603 }
1604 }
1605 );
1606 updateAllSmartPostFilterCounts(
1607 blockParent
1608 );
1609 } else {
1610 resetFilterCounts(blockParent);
1611 }
1612 socialLinkCopy();
1613 modal();
1614 initTimelineScroll();
1615 initSmartPostFeatures();
1616 if (preloader) {
1617 // Wait 2.5 seconds before showing fallback
1618 setTimeout(() => {
1619 preloader.classList.remove(
1620 'sp-d-block'
1621 );
1622 preloader.classList.add(
1623 'sp-d-hidden'
1624 );
1625 container.classList.add(
1626 'sp-smart-preloader-removed'
1627 );
1628 }, 200);
1629 }
1630 if (loadMoreWrapper) {
1631 const no_more_button =
1632 loadMoreWrapper.querySelector(
1633 '.sps-no-more-post'
1634 );
1635 if (no_more_button) {
1636 const loadMoreWrapperBTN =
1637 loadMoreWrapper.querySelector(
1638 'a'
1639 );
1640 if (loadMoreWrapperBTN) {
1641 loadMoreWrapperBTN.style.display =
1642 'flex';
1643 }
1644 no_more_button.style.display =
1645 'none';
1646 }
1647 }
1648 });
1649 }
1650 }
1651 }
1652
1653 // ---------- Navigation ----------
1654 calendar
1655 .querySelector('.sps-date-filter-nav-prev')
1656 ?.addEventListener('click', (e) => {
1657 e.stopPropagation();
1658 currentDate.setMonth(currentDate.getMonth() - 1);
1659 renderCalendar();
1660 });
1661
1662 calendar
1663 .querySelector('.sps-date-filter-nav-next')
1664 ?.addEventListener('click', (e) => {
1665 e.stopPropagation();
1666 currentDate.setMonth(currentDate.getMonth() + 1);
1667 renderCalendar();
1668 });
1669 // ---------- Month List ----------
1670 monthNames.forEach((m, i) => {
1671 const btn = document.createElement('button');
1672 btn.textContent = m;
1673 btn.onclick = (e) => {
1674 e.stopPropagation();
1675 currentDate.setMonth(i);
1676 renderCalendar();
1677 monthList.style.display = 'none';
1678 };
1679 monthList.appendChild(btn);
1680 });
1681
1682 // ---------- Year List ----------
1683 const currentYear = new Date().getFullYear();
1684 for (let y = currentYear; y >= 2000; y--) {
1685 const btn = document.createElement('button');
1686 btn.textContent = y;
1687 btn.onclick = (e) => {
1688 e.stopPropagation();
1689 currentDate.setFullYear(y);
1690 renderCalendar();
1691 yearList.style.display = 'none';
1692 };
1693 yearList.appendChild(btn);
1694 }
1695
1696 // ---------- Toggles ----------
1697 const monthTrigger = calendar.querySelector(
1698 '.sps-date-filter-month-trigger'
1699 );
1700 if (monthTrigger && !monthTrigger.dataset.bound) {
1701 monthTrigger.dataset.bound = 'true';
1702 monthTrigger.addEventListener('click', (e) => {
1703 e.stopPropagation();
1704 monthList.style.display =
1705 monthList.style.display === 'block'
1706 ? 'none'
1707 : 'block';
1708 yearList.style.display = 'none';
1709 });
1710 }
1711
1712 const yearTrigger = calendar.querySelector(
1713 '.sps-date-filter-year-trigger'
1714 );
1715 if (yearTrigger && !yearTrigger.dataset.bound) {
1716 yearTrigger.dataset.bound = 'true';
1717 yearTrigger?.addEventListener('click', (e) => {
1718 e.stopPropagation();
1719 yearList.style.display =
1720 yearList.style.display === 'block'
1721 ? 'none'
1722 : 'block';
1723 monthList.style.display = 'none';
1724 });
1725 }
1726
1727 document.addEventListener('click', (e) => {
1728 if (!calendar.contains(e.target)) {
1729 monthList.style.display = 'none';
1730 yearList.style.display = 'none';
1731 }
1732 });
1733
1734 renderCalendar();
1735 });
1736 }
1737
1738 function datePreset() {
1739 const dateilter = blockParent.querySelector(
1740 '.sp-smart-post-date-filter-main'
1741 );
1742 if (dateilter) {
1743 dateilter.querySelectorAll('a[data-value]').forEach((link) => {
1744 const dropdown = blockParent.querySelector(
1745 '.sp-smart-post-live-filter-dropdown-date, .sp-smart-post-live-filter-button-date'
1746 );
1747
1748 const presetType = dropdown.dataset.presetType;
1749
1750 const button = blockParent.querySelector(
1751 '.sp-smart-post-date-filter > .sp-smart-post-live-filter-btn'
1752 );
1753
1754 link.addEventListener('click', function (e) {
1755 e.preventDefault();
1756 blockParent
1757 .querySelectorAll('a[data-value]')
1758 .forEach((l) => l.classList.remove('active'));
1759
1760 this.classList.add('active');
1761
1762 if (presetType === 'dropdown') {
1763 const label = button.querySelector(
1764 '.sp-smart-post-show-date-filter-date-value'
1765 );
1766
1767 if (label) label.textContent = this.textContent;
1768
1769 dropdown.classList.remove('open');
1770 button.setAttribute('aria-expanded', 'false');
1771 }
1772
1773 sps_preset_date = this.dataset.value;
1774 currentState['sps_preset_date'] = sps_preset_date;
1775
1776 updateURLQuery(currentState);
1777
1778 if (preloader) {
1779 // Wait 2.5 seconds before showing fallback
1780 preloader.classList.add('sp-d-block');
1781 container.classList.remove(
1782 'sp-smart-preloader-removed'
1783 );
1784 }
1785 let itemsEl = null;
1786 delete currentState.html;
1787 loadQueryData(currentState).then((data) => {
1788 if (!data.success) {
1789 return;
1790 }
1791
1792 if (pagination_section) {
1793 pagination_section.dataset.current = 1;
1794 }
1795 if (
1796 sps_marquee &&
1797 containerEl.classList.contains(
1798 'sp_marquee-container'
1799 )
1800 ) {
1801 container.querySelector(
1802 '.sp_marquee-content'
1803 ).innerHTML = data.html;
1804 sps_marquee = new SP_Marquee(
1805 containerEl,
1806 sps_marquee_option
1807 );
1808 } else if (sps_swiper) {
1809 // if (sps_swiper.slides && sps_swiper.slides.length > 0) {
1810 // // sps_swiper.removeAllSlides();
1811 // }
1812 sps_swiper.destroy(true);
1813 container.querySelector(
1814 '.swiper-wrapper'
1815 ).innerHTML = data.html;
1816 if (
1817 sps_swiper_option.effect &&
1818 ['fade', 'cube', 'flip'].includes(
1819 sps_swiper_option?.effect
1820 )
1821 ) {
1822 wrapSlidesForEffect(
1823 containerEl,
1824 sps_swiper_option
1825 );
1826 }
1827 sps_swiper = new PCPSwiper(
1828 containerEl,
1829 sps_swiper_option
1830 );
1831 } else {
1832 const items = container.querySelector(
1833 '.sp-smart-post-items'
1834 );
1835 items.innerHTML = data.html;
1836 itemsEl = items;
1837 if (itemsEl) {
1838 const childItems = itemsEl.querySelectorAll(
1839 '.sp-smart-post-card, .swiper-slide'
1840 );
1841 childItems.forEach((el, index) => {
1842 el.style.opacity = 0;
1843 el.style.animation = `fadeIn 0.7s ease-in-out forwards`;
1844 el.style.animationDelay = `${index * 0.1}s`; // 80ms stagger
1845 });
1846 }
1847 }
1848 const termCounts = data.count_data;
1849 if (termCounts && hasFilterCounts(termCounts)) {
1850 updateFilterCounts(
1851 termCounts,
1852 pagination_section,
1853 blockParent
1854 );
1855 updateAllSmartPostFilterCounts(blockParent);
1856 } else if (Object.keys(currentState).length > 0) {
1857 blockParent
1858 .querySelectorAll(
1859 '.sp-smart-post-taxonomy-filter select option'
1860 )
1861 .forEach((option) => {
1862 const slug = option.value;
1863 if (slug) {
1864 option.textContent = `${capitalize(
1865 slug
1866 )} (0)`;
1867 option.disabled = true;
1868 }
1869 });
1870 Object.entries(termCounts).forEach(
1871 ([taxonomy, terms]) => {
1872 // Handle pagination update
1873 if (
1874 taxonomy === 'pagination' &&
1875 pagination_section
1876 ) {
1877 updatePaginationData(
1878 pagination_section,
1879 terms
1880 );
1881 }
1882 }
1883 );
1884 updateAllSmartPostFilterCounts(blockParent);
1885 } else {
1886 resetFilterCounts(blockParent);
1887 }
1888 socialLinkCopy();
1889 modal();
1890 initTimelineScroll();
1891 initSmartPostFeatures();
1892 if (preloader) {
1893 // Wait 2.5 seconds before showing fallback
1894 setTimeout(() => {
1895 preloader.classList.remove('sp-d-block');
1896 preloader.classList.add('sp-d-hidden');
1897 container.classList.add(
1898 'sp-smart-preloader-removed'
1899 );
1900 }, 200);
1901 }
1902 if (loadMoreWrapper) {
1903 const no_more_button =
1904 loadMoreWrapper.querySelector(
1905 '.sps-no-more-post'
1906 );
1907 if (no_more_button) {
1908 const loadMoreWrapperBTN =
1909 loadMoreWrapper.querySelector('a');
1910 if (loadMoreWrapperBTN) {
1911 loadMoreWrapperBTN.style.display =
1912 'flex';
1913 }
1914 no_more_button.style.display = 'none';
1915 }
1916 }
1917 });
1918 });
1919 });
1920 }
1921 }
1922
1923 function resetTaxonomyFilter() {
1924 const taxonomyEl = blockParent.querySelector(
1925 '.sp-smart-post-taxonomy-filter'
1926 );
1927
1928 if (!taxonomyEl) {
1929 return;
1930 }
1931
1932 // Reset button text
1933 const allText = taxonomyEl.dataset.allText || 'All';
1934
1935 const btnTextEl = taxonomyEl.querySelector(
1936 '.sp-smart-post-live-filter-btn span'
1937 );
1938
1939 if (btnTextEl) {
1940 btnTextEl.textContent = allText;
1941 }
1942
1943 // Reset active state
1944 const links = taxonomyEl.querySelectorAll(
1945 '.sps-live-filter-nav-link'
1946 );
1947
1948 links.forEach((link) => {
1949 link.classList.remove('active');
1950 link.removeAttribute('aria-current');
1951 });
1952
1953 // Activate default ("All")
1954 const allLink = taxonomyEl.querySelector(
1955 '.sps-live-filter-nav-link[data-value=""]'
1956 );
1957
1958 if (allLink) {
1959 allLink.classList.add('active');
1960 allLink.setAttribute('aria-current', 'true');
1961 }
1962
1963 // Reset hidden select (if exists)
1964 const select = taxonomyEl.querySelector('select');
1965 if (select) {
1966 select.value = '';
1967 }
1968 }
1969
1970 function resetSortFilter() {
1971 const sortEl = blockParent.querySelector(
1972 '.sp-smart-post-sort-filter'
1973 );
1974
1975 if (!sortEl) {
1976 return;
1977 }
1978
1979 // Reset button text
1980 const placeholder = sortEl.dataset.sortPlaceholder || 'None';
1981
1982 const btnTextEl = sortEl.querySelector(
1983 '.sp-smart-post-live-filter-btn span'
1984 );
1985
1986 if (btnTextEl) {
1987 btnTextEl.textContent = placeholder;
1988 }
1989
1990 // Reset active items
1991 const links = sortEl.querySelectorAll('.sps-live-filter-nav-link');
1992
1993 links.forEach((link) => {
1994 link.classList.remove('active');
1995 link.removeAttribute('aria-current');
1996 });
1997
1998 // Activate "None"
1999
2000 const noneLink = sortEl.querySelector(
2001 '.sps-live-filter-nav-link[data-value=""]'
2002 );
2003
2004 if (noneLink) {
2005 noneLink.classList.add('active');
2006 noneLink.setAttribute('aria-current', 'true');
2007 }
2008
2009 // Sync hidden select (IMPORTANT)
2010 const select = sortEl.querySelector('select[name="sps_sort"]');
2011 if (select) {
2012 select.value = '';
2013 }
2014 }
2015
2016 function resetDateFilter() {
2017 const dateFilterEl = blockParent.querySelector(
2018 '.sp-smart-post-date-filter'
2019 );
2020
2021 if (!dateFilterEl) {
2022 return;
2023 }
2024
2025 // Reset button text
2026 const button = dateFilterEl.querySelector(
2027 '.sp-smart-post-live-filter-btn'
2028 );
2029
2030 if (button) {
2031 const label = button.querySelector(
2032 '.sp-smart-post-show-date-filter-date-value'
2033 );
2034 const defaultLabel = label.dataset.defaultLabel || 'Select';
2035 if (label) label.textContent = defaultLabel;
2036 }
2037
2038 // Reset active state in dropdown
2039 const links = dateFilterEl.querySelectorAll('a[data-value]');
2040 links.forEach((link) => {
2041 link.classList.remove('active');
2042 link.removeAttribute('aria-current');
2043 });
2044
2045 // Activate default (empty value or first item)
2046 const defaultLink = dateFilterEl.querySelector('a[data-value=""]');
2047 if (defaultLink) {
2048 defaultLink.classList.add('active');
2049 defaultLink.setAttribute('aria-current', 'true');
2050 }
2051
2052 // Reset global state
2053 currentState.sps_preset_date = '';
2054 sps_preset_date = '';
2055 fromDate = null;
2056 toDate = null;
2057 datePicker();
2058 }
2059
2060 function clearFilter() {
2061 const clearFilterWrapper = blockParent.querySelectorAll(
2062 '.sp-smart-post-clear-filter-main'
2063 );
2064
2065 clearFilterWrapper.forEach((wrapper) => {
2066 wrapper.addEventListener('click', async (e) => {
2067 e.preventDefault();
2068
2069 // RESET GLOBAL FILTER STATE
2070 currentState = {
2071 block: blockId,
2072 block_location,
2073 page_id,
2074 relation: filter_relation,
2075 sps_search: '',
2076 sps_sort: '',
2077 tx_category: '',
2078 };
2079
2080 // Reset globals
2081 sps_date = '';
2082 sps_preset_date = '';
2083
2084 // RESET UI
2085 resetTaxonomyFilter();
2086 resetSortFilter();
2087 resetDateFilter();
2088
2089 const searchInput = blockParent.querySelector(
2090 '.sp-smart-post-search-field > input'
2091 );
2092 if (searchInput) searchInput.value = '';
2093
2094 // UPDATE URL (ONCE)
2095 updateURLQuery(currentState);
2096
2097 // SHOW PRELOADER
2098 if (preloader) {
2099 preloader.classList.add('sp-d-block');
2100 container.classList.remove(
2101 'sp-smart-preloader-removed'
2102 );
2103 }
2104
2105 const data = await loadQueryData(currentState);
2106 if (!data?.success) return;
2107
2108 // RESET PAGINATION
2109 if (pagination_section) {
2110 pagination_section.dataset.current = 1;
2111 }
2112
2113 // RENDER CONTENT
2114 if (
2115 sps_marquee &&
2116 containerEl.classList.contains('sp_marquee-container')
2117 ) {
2118 container.querySelector(
2119 '.sp_marquee-content'
2120 ).innerHTML = data.html;
2121 sps_marquee = new SP_Marquee(
2122 containerEl,
2123 sps_marquee_option
2124 );
2125 } else if (sps_swiper) {
2126 sps_swiper.destroy(true);
2127 container.querySelector('.swiper-wrapper').innerHTML =
2128 data.html;
2129
2130 if (
2131 ['fade', 'cube', 'flip'].includes(
2132 sps_swiper_option?.effect
2133 )
2134 ) {
2135 wrapSlidesForEffect(containerEl, sps_swiper_option);
2136 }
2137
2138 sps_swiper = new PCPSwiper(
2139 containerEl,
2140 sps_swiper_option
2141 );
2142 } else {
2143 const items = container.querySelector(
2144 '.sp-smart-post-items'
2145 );
2146 items.innerHTML = data.html;
2147
2148 items
2149 .querySelectorAll(
2150 '.sp-smart-post-card, .swiper-slide'
2151 )
2152 .forEach((el, index) => {
2153 el.style.opacity = 0;
2154 el.style.animation =
2155 'fadeIn 0.7s ease-in-out forwards';
2156 el.style.animationDelay = `${index * 0.1}s`;
2157 });
2158 }
2159
2160 // UPDATE COUNTS
2161 if (data.count_data && hasFilterCounts(data.count_data)) {
2162 updateFilterCounts(
2163 data.count_data,
2164 pagination_section,
2165 blockParent
2166 );
2167 updateAllSmartPostFilterCounts(blockParent);
2168 } else {
2169 resetFilterCounts(blockParent);
2170 }
2171
2172 // RE-INIT HELPERS
2173 socialLinkCopy();
2174 modal();
2175 initTimelineScroll();
2176 initSmartPostFeatures();
2177
2178 // HIDE PRELOADER
2179 if (preloader) {
2180 setTimeout(() => {
2181 preloader.classList.remove('sp-d-block');
2182 preloader.classList.add('sp-d-hidden');
2183 container.classList.add(
2184 'sp-smart-preloader-removed'
2185 );
2186 }, 200);
2187 }
2188
2189 // RESET LOAD MORE
2190 if (loadMoreWrapper) {
2191 const noMoreBtn =
2192 loadMoreWrapper.querySelector('.sps-no-more-post');
2193 const loadMoreBtn = loadMoreWrapper.querySelector('a');
2194
2195 if (loadMoreBtn) loadMoreBtn.style.display = 'flex';
2196 if (noMoreBtn) noMoreBtn.style.display = 'none';
2197 }
2198 });
2199 });
2200 }
2201
2202 // Listen to filter field changes
2203 if (page_id && filter_fields.length > 0) {
2204 currentState.html = false;
2205
2206 loadQueryData(currentState)
2207 .then((data) => {
2208 if (!data.success) {
2209 return;
2210 }
2211 const termCounts = data.count_data;
2212 if (termCounts && hasFilterCounts(termCounts)) {
2213 updateFilterCounts(termCounts, false, blockParent);
2214 updateAllSmartPostFilterCounts(blockParent);
2215 } else if (Object.keys(currentState).length > 0) {
2216 blockParent
2217 .querySelectorAll(
2218 '.sp-smart-filter-area select option'
2219 )
2220 .forEach((option) => {
2221 const slug = option.value;
2222 if (slug) {
2223 option.textContent = `${capitalize(
2224 slug
2225 )} (0)`;
2226 option.disabled = true;
2227 }
2228 });
2229 } else {
2230 resetFilterCounts(blockParent);
2231 }
2232 initTimelineScroll();
2233 })
2234 .catch();
2235 const eventHandler = (e) => {
2236 currentState = getFilterState(e);
2237 if (preloader) {
2238 // Wait 2.5 seconds before showing fallback
2239 preloader.classList.add('sp-d-block');
2240 container.classList.remove('sp-smart-preloader-removed');
2241 }
2242 let itemsEl = null;
2243
2244 loadQueryData(currentState).then((data) => {
2245 if (!data.success) {
2246 return;
2247 }
2248
2249 if (pagination_section) {
2250 pagination_section.dataset.current = 1;
2251 }
2252 if (
2253 sps_marquee &&
2254 containerEl.classList.contains('sp_marquee-container')
2255 ) {
2256 container.querySelector(
2257 '.sp_marquee-content'
2258 ).innerHTML = data.html;
2259 sps_marquee = new SP_Marquee(
2260 containerEl,
2261 sps_marquee_option
2262 );
2263 } else if (sps_swiper) {
2264 // if (sps_swiper.slides && sps_swiper.slides.length > 0) {
2265 // // sps_swiper.removeAllSlides();
2266 // }
2267
2268 sps_swiper.destroy(true);
2269 container.querySelector('.swiper-wrapper').innerHTML =
2270 data.html;
2271 if (
2272 sps_swiper_option.effect &&
2273 ['fade', 'cube', 'flip'].includes(
2274 sps_swiper_option?.effect
2275 )
2276 ) {
2277 wrapSlidesForEffect(containerEl, sps_swiper_option);
2278 }
2279 sps_swiper = new PCPSwiper(
2280 containerEl,
2281 sps_swiper_option
2282 );
2283 } else {
2284 const items = container.querySelector(
2285 '.sp-smart-post-items'
2286 );
2287 items.innerHTML = data.html;
2288 itemsEl = items;
2289 // container.querySelector(
2290 // '.sp-smart-post-items'
2291 // ).style.opacity = '1';
2292 // Staggered Animation
2293 if (itemsEl) {
2294 const childItems = itemsEl.querySelectorAll(
2295 '.sp-smart-post-card, .swiper-slide'
2296 );
2297 childItems.forEach((el, index) => {
2298 el.style.opacity = 0;
2299 el.style.animation = `fadeIn 0.7s ease-in-out forwards`;
2300 el.style.animationDelay = `${index * 0.1}s`; // 80ms stagger
2301 });
2302 }
2303 }
2304 const termCounts = data.count_data;
2305 if (termCounts && hasFilterCounts(termCounts)) {
2306 updateFilterCounts(
2307 termCounts,
2308 pagination_section,
2309 blockParent
2310 );
2311 updateAllSmartPostFilterCounts(blockParent);
2312 } else if (Object.keys(currentState).length > 0) {
2313 blockParent
2314 .querySelectorAll(
2315 '.sp-smart-post-taxonomy-filter select option'
2316 )
2317 .forEach((option) => {
2318 const slug = option.value;
2319 if (slug) {
2320 option.textContent = `${capitalize(
2321 slug
2322 )} (0)`;
2323 option.disabled = true;
2324 }
2325 });
2326 Object.entries(termCounts).forEach(
2327 ([taxonomy, terms]) => {
2328 // Handle pagination update
2329 if (
2330 taxonomy === 'pagination' &&
2331 pagination_section
2332 ) {
2333 updatePaginationData(
2334 pagination_section,
2335 terms
2336 );
2337 }
2338 }
2339 );
2340 updateAllSmartPostFilterCounts(blockParent);
2341 } else {
2342 resetFilterCounts(blockParent);
2343 }
2344 socialLinkCopy();
2345 modal();
2346 initTimelineScroll();
2347 initSmartPostFeatures();
2348 if (preloader) {
2349 // Wait 2.5 seconds before showing fallback
2350 setTimeout(() => {
2351 preloader.classList.remove('sp-d-block');
2352 preloader.classList.add('sp-d-hidden');
2353 container.classList.add(
2354 'sp-smart-preloader-removed'
2355 );
2356 }, 200);
2357 }
2358 if (loadMoreWrapper) {
2359 const no_more_button =
2360 loadMoreWrapper.querySelector('.sps-no-more-post');
2361 if (no_more_button) {
2362 const loadMoreWrapperBTN =
2363 loadMoreWrapper.querySelector('a');
2364 if (loadMoreWrapperBTN) {
2365 loadMoreWrapperBTN.style.display = 'flex';
2366 }
2367 no_more_button.style.display = 'none';
2368 }
2369 }
2370 });
2371 };
2372 filter_fields.forEach((field) => {
2373 const isInput = field.tagName === 'INPUT';
2374 const eventName = isInput ? 'input' : 'change';
2375
2376 // Add event listener.
2377 field.addEventListener(
2378 eventName,
2379 isInput ? debounce(eventHandler, 400) : eventHandler
2380 );
2381 });
2382 datePreset();
2383 datePicker();
2384 clearFilter();
2385 }
2386
2387 function dateFilterToggle() {
2388
2389 const mainFilters = blockParent
2390 ?.querySelectorAll('.sp-smart-post-date-filter-main');
2391
2392 mainFilters?.forEach((wrapper) => {
2393 const button = wrapper.querySelector(
2394 '.sp-smart-post-live-filter-btn'
2395 );
2396 const dropdown = wrapper.querySelector(
2397 '.sp-smart-post-live-filter-dropdown-date'
2398 );
2399 if (!button || !dropdown) return;
2400 // Toggle dropdown
2401 button.addEventListener('click', function (e) {
2402 e.preventDefault();
2403 e.stopPropagation();
2404 dropdown.classList.toggle('open');
2405 button.setAttribute(
2406 'aria-expanded',
2407 dropdown.classList.contains('open')
2408 ? 'true'
2409 : 'false'
2410 );
2411 });
2412 // Close on outside click
2413 document.addEventListener('click', function (e) {
2414 if (!wrapper.contains(e.target)) {
2415 dropdown.classList.remove('open');
2416 button.setAttribute('aria-expanded', 'false');
2417 }
2418 });
2419 });
2420 }
2421
2422 dateFilterToggle();
2423 /**
2424 * SECTION 3: PAGINATION HANDLING
2425 */
2426
2427 if (!pagination_section) {
2428 return;
2429 }
2430 let current = parseInt(pagination_section.dataset.current, 10);
2431 let total = parseInt(pagination_section.dataset.pages, 10);
2432 const endMessage = pagination_section.dataset.endmessage ?? '';
2433 const pagination_type = pagination_section.dataset.paginationtype;
2434 let contents = container.querySelector(
2435 pagination_type === 'load-more'
2436 ? '.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'
2437 : '.sp-smart-post-items,.sp-smart-post-list-item'
2438 );
2439
2440 const updateCurrent = (newPage) => {
2441 pagination_section.dataset.current = newPage;
2442 current = newPage;
2443 };
2444 const appendHtml = (html) => {
2445 contents.insertAdjacentHTML('beforeend', html);
2446 return contents;
2447 };
2448
2449 const replaceHtml = (html) => {
2450 contents.innerHTML = html;
2451 return contents;
2452 };
2453
2454 const loadPage = (page, mode = 'replace', currentState = {}) => {
2455 total = parseInt(pagination_section.dataset.pages, 10);
2456 if (page < 1 || page > total || page === current) {
2457 return;
2458 }
2459 contents = container.querySelector(
2460 pagination_type === 'load-more'
2461 ? '.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'
2462 : '.sp-smart-post-items,.sp-smart-post-list-item'
2463 );
2464 currentState = {
2465 ...currentState,
2466 sps_page: page,
2467 pagination_type,
2468 block: blockId,
2469 page_id,
2470 };
2471 const infinitePreloader = pagination_section.querySelector(
2472 '.sp-smart-post-show-preloading'
2473 );
2474 const buttonPreloader = pagination_section.querySelector(
2475 '.sp-smart-post-show-preloader'
2476 );
2477
2478 // Show the correct preloader
2479 if (isInfiniteScroll) {
2480 if (infinitePreloader) {
2481 infinitePreloader.style.display = 'block';
2482 }
2483 } else {
2484 if (buttonPreloader) {
2485 // buttonPreloader.style.display = 'block';
2486 buttonPreloader?.classList?.remove('sp-d-hidden');
2487 }
2488 buttonPreloader?.classList?.add('sp-d-block');
2489 }
2490
2491 loadQueryData(currentState)
2492 .then((data) => {
2493 if (!data.success) {
2494 return;
2495 }
2496 if (mode === 'append') {
2497 // Get items count before append.
2498 const existingCount = contents.querySelectorAll(
2499 '.sp-smart-post-card, .swiper-slide'
2500 ).length;
2501 // Append new HTML.
2502 appendHtml(data.html);
2503 // Get all items after append.
2504 const allItems = contents.querySelectorAll(
2505 '.sp-smart-post-card, .swiper-slide'
2506 );
2507 // Animate only the new ones.
2508 allItems.forEach((el, index) => {
2509 if (index >= existingCount) {
2510 el.style.opacity = 0;
2511 el.style.animation = `fadeIn 0.7s ease-in-out forwards`;
2512 el.style.animationDelay = `${
2513 (index - existingCount) * 0.1
2514 }s`;
2515 }
2516 });
2517 } else {
2518 // Replace mode — animate all.
2519 replaceHtml(data.html);
2520 const childItems = contents.querySelectorAll(
2521 '.sp-smart-post-card, .swiper-slide'
2522 );
2523 childItems.forEach((el, index) => {
2524 el.style.opacity = 0;
2525 el.style.animation = `fadeIn 0.7s ease-in-out forwards`;
2526 el.style.animationDelay = `${index * 0.1}s`;
2527 });
2528 }
2529 updateCurrent(page);
2530 updatePaginationUI(pagination_section);
2531 socialLinkCopy();
2532 initSmartPostFeatures();
2533 modal();
2534 initTimelineScroll();
2535 loadMoreWrapper?.classList.remove('sps_disabled');
2536 if (isInfiniteScroll) {
2537 if (infinitePreloader) {
2538 infinitePreloader.style.display = 'none';
2539 }
2540 } else {
2541 if (buttonPreloader) {
2542 // buttonPreloader.style.display = 'none';
2543 buttonPreloader?.classList?.remove('sp-d-block');
2544 }
2545 buttonPreloader?.classList?.add('sp-d-hidden');
2546 }
2547 if (page >= total && loadMoreWrapper) {
2548 loadMoreWrapper.insertAdjacentHTML(
2549 'beforeend',
2550 `<span class="sps-no-more-post">${endMessage}</span>`
2551 );
2552 const loadMoreWrapperBtn =
2553 loadMoreWrapper.querySelector('a');
2554 loadMoreWrapperBtn.style.display = 'none';
2555 }
2556 })
2557 .catch((err) => console.log('Pagination Error:', err));
2558 };
2559
2560 const loadMoreBtn = loadMoreWrapper?.querySelector('a');
2561 const isInfiniteScroll = !!loadMoreWrapper?.querySelector(
2562 '.sp-smart-post-show-preloading'
2563 );
2564
2565 if (isInfiniteScroll && 'IntersectionObserver' in window) {
2566 const observer = new IntersectionObserver(
2567 (entries) => {
2568 if (entries[0].isIntersecting) {
2569 current = parseInt(
2570 pagination_section.dataset.current,
2571 10
2572 );
2573 if (current < total) {
2574 currentState = getFilterState(
2575 null,
2576 true,
2577 pagination_section
2578 );
2579 // Show preloader
2580 const preloader = loadMoreWrapper.querySelector(
2581 '.sp-smart-post-show-preloading'
2582 );
2583 if (preloader) {
2584 preloader.style.display = 'block';
2585 }
2586 loadPage(current + 1, 'append', currentState);
2587 }
2588 }
2589 },
2590 { rootMargin: '100px' }
2591 );
2592 if (loadMoreWrapper) {
2593 observer.observe(loadMoreWrapper);
2594 }
2595 } else if (loadMoreBtn) {
2596 const load_more_preloader = loadMoreWrapper?.querySelector(
2597 '.sp-smart-post-show-preloader'
2598 );
2599 loadMoreBtn.addEventListener('click', (e) => {
2600 e.preventDefault();
2601 // load_more_preloader.style.display = 'block';
2602 load_more_preloader.classList.remove('sp-d-hidden');
2603 load_more_preloader.classList.add('sp-d-block');
2604 // Disable the button
2605 loadMoreWrapper.classList.add('sps_disabled');
2606
2607 // loadMoreBtn.textContent = 'Loading...';
2608 current = parseInt(pagination_section.dataset.current, 10);
2609
2610 currentState = getFilterState(e, false);
2611
2612 loadPage(current + 1, 'append', currentState);
2613 });
2614 }
2615 const paginationContainer = pagination_section.querySelector(
2616 '.sp-smart-post-pagination-buttons'
2617 );
2618 paginationContainer?.addEventListener('click', (e) => {
2619 const btn = e.target.closest('.page-numbers');
2620 if (
2621 !btn ||
2622 btn.classList.contains('disabled') ||
2623 btn.classList.contains('current')
2624 ) {
2625 return;
2626 }
2627 e.preventDefault();
2628 const currentPage = parseInt(
2629 pagination_section.dataset.current,
2630 10
2631 );
2632 let newPage = null;
2633
2634 if (btn.classList.contains('prev')) {
2635 newPage = currentPage - 1;
2636 } else if (btn.classList.contains('next')) {
2637 newPage = currentPage + 1;
2638 } else {
2639 newPage = parseInt(btn.dataset.page, 10);
2640 }
2641
2642 if (isNaN(newPage) || newPage === currentPage) {
2643 return;
2644 }
2645 // Get current filter state (if you have one).
2646
2647 const currentState = getFilterState(e);
2648 // Load the new page.
2649
2650 loadPage(newPage, 'replace', currentState);
2651 // Optional: update the data-current attribute.
2652
2653 pagination_section.dataset.current = newPage;
2654 });
2655 // Grid Nav Arrows (custom arrow buttons)
2656 const navArrows = pagination_section.querySelectorAll(
2657 '.sp-smart-post-grid-nav-arrow-btn'
2658 );
2659 if (navArrows.length === 2) {
2660 const [prevArrow, nextArrow] = navArrows;
2661 prevArrow.addEventListener('click', (e) => {
2662 e.preventDefault();
2663 currentState = getFilterState(e);
2664 loadPage(current - 1, 'replace', currentState);
2665 });
2666 nextArrow.addEventListener('click', (e) => {
2667 e.preventDefault();
2668 currentState = getFilterState(e);
2669 current = parseInt(pagination_section.dataset.current, 10);
2670 loadPage(current + 1, 'replace', currentState);
2671 });
2672 }
2673
2674 updatePaginationUI(pagination_section);
2675 });
2676
2677 /**
2678 * Capitalizes the first letter of a string.
2679 *
2680 * @param {string} str - The string to capitalize.
2681 * @return {string} - Capitalized string.
2682 */
2683 function capitalize(str) {
2684 if (!str) {
2685 return '';
2686 }
2687 return str.charAt(0).toUpperCase() + str.slice(1);
2688 }
2689
2690 /**
2691 * Updates the filter <select> options with post counts from the response.
2692 *
2693 * @param {Object} termCounts - Term count data (taxonomy → terms[]).
2694 * @param {HTMLElement|null} pagination_section - Pagination DOM section (if exists).
2695 * @param {HTMLElement} blockParent - The block wrapper containing filters.
2696 */
2697 function updateFilterCounts(termCounts, pagination_section, blockParent) {
2698 if (!termCounts || !blockParent) {
2699 return;
2700 }
2701 const show_count = true;
2702 Object.entries(termCounts).forEach(([taxonomy, terms]) => {
2703 // Handle pagination update
2704 if (taxonomy === 'pagination' && pagination_section) {
2705 updatePaginationData(pagination_section, terms);
2706 return;
2707 }
2708
2709 // Find the filter select dropdown for the taxonomy
2710 const select = blockParent.querySelector(`#filter-${taxonomy}`);
2711 if (!select) {
2712 return;
2713 }
2714
2715 Array.from(select.options).forEach((option) => {
2716 const slug = option.value;
2717 if (!slug || !terms) {
2718 return;
2719 }
2720 const isSelected = option.selected;
2721 const defaultLabel = option.getAttribute('data-label') || slug;
2722 const term = terms.find((t) => t.slug == slug);
2723 if (term) {
2724 const count = show_count ? `(${term.count})` : '';
2725 // Term exists: show its updated count.
2726 option.textContent = `${capitalize(defaultLabel)} ${count}`;
2727 option.disabled = false;
2728 } else {
2729 // Term not in result: disable unless selected.
2730 const count = show_count ? `(0)` : '';
2731 option.textContent = `${capitalize(defaultLabel)} (0)`;
2732 option.disabled = !isSelected;
2733 }
2734 });
2735 });
2736 }
2737
2738 /**
2739 * Resets all filter dropdowns to their default counts (used when no filters are active).
2740 *
2741 * @param {HTMLElement} blockParent - The block wrapper containing filters.
2742 */
2743 function resetFilterCounts(blockParent) {
2744 if (!blockParent) {
2745 return;
2746 }
2747
2748 blockParent
2749 .querySelectorAll('.sp-smart-filter-area select option')
2750 .forEach((option) => {
2751 const slug = option.value;
2752 if (!slug) {
2753 return;
2754 } // Skip the "All" or blank option
2755
2756 const defaultCount = option.getAttribute('data-default-count');
2757 if (defaultCount !== null) {
2758 option.textContent = `${capitalize(
2759 option.getAttribute('data-label') || slug
2760 )} (${defaultCount})`;
2761 option.disabled = false;
2762 }
2763 });
2764 }
2765 //-------------------------------------
2766
2767 const filterWrappers = document.querySelectorAll(
2768 '.sp-smart-post-taxonomy-filter, .sp-smart-post-sort-filter'
2769 );
2770
2771 // Common handler function for dropdown items
2772 function handleDropdownClick(
2773 e,
2774 dropdown,
2775 oppositeDropdown,
2776 spanLabel,
2777 select
2778 ) {
2779 const item = e.target.closest('a');
2780 if (!item || item.classList.contains('disabled')) return;
2781
2782 e.preventDefault();
2783
2784 // Remove active class from opposite dropdown if exists
2785 if (oppositeDropdown) {
2786 const links = oppositeDropdown.querySelectorAll(
2787 '.sps-live-filter-nav-link'
2788 );
2789 links.forEach((link) => link.classList.remove('active'));
2790 }
2791
2792 // Remove active from all items in current dropdown
2793 dropdown
2794 .querySelectorAll('a')
2795 .forEach((el) => el.classList.remove('active'));
2796
2797 // Add active to clicked item
2798 item.classList.add('active');
2799
2800 // Extract values
2801 const value = item.getAttribute('data-value');
2802 const label = item.getAttribute('data-label');
2803 const countMatch = item.textContent.match(/\((\d+)\)/);
2804 const count = countMatch ? countMatch[1] : '';
2805
2806 // Update button label
2807 if (spanLabel) {
2808 spanLabel.innerHTML = `${label}${count ? ` (${count})` : ''}`;
2809 }
2810
2811 // Update select and trigger change
2812 if (select) {
2813 select.value = value;
2814 select.dispatchEvent(new Event('change'));
2815 }
2816
2817 // Close dropdown
2818 dropdown.classList.remove('open');
2819 }
2820
2821 // Setup function for each filter wrapper
2822 function setupFilterWrapper(wrapper) {
2823 const btn = wrapper.querySelector('.sp-smart-post-live-filter-btn');
2824 const dropdown = wrapper.querySelector(
2825 '.sp-smart-post-live-filter-dropdown, .sp-smart-post-live-filter-button, .sp-smart-post-live-filter-button-date'
2826 );
2827 const secondDropdown = wrapper.querySelector(
2828 '.sps-live-filter-dropdown-menu'
2829 );
2830 const spanLabel = btn?.querySelector('span');
2831
2832 // Find the closest hidden select
2833 const select = wrapper
2834 .closest('.sp-smart-post-live-filter-wrapper')
2835 ?.querySelector('select');
2836
2837 if (!select) {
2838 return;
2839 }
2840
2841 // Setup dropdown toggle
2842 if (btn && dropdown) {
2843 btn.addEventListener('click', (e) => {
2844 e.preventDefault();
2845 dropdown.classList.toggle('open');
2846 });
2847 }
2848
2849 // Setup dropdown click handlers
2850 if (dropdown) {
2851 dropdown.addEventListener('click', (e) =>
2852 handleDropdownClick(
2853 e,
2854 dropdown,
2855 secondDropdown,
2856 spanLabel,
2857 select
2858 )
2859 );
2860 }
2861
2862 if (secondDropdown) {
2863 secondDropdown.addEventListener('click', (e) =>
2864 handleDropdownClick(
2865 e,
2866 secondDropdown,
2867 dropdown,
2868 spanLabel,
2869 select
2870 )
2871 );
2872 }
2873 }
2874
2875 // Initialize all filter wrappers
2876 filterWrappers.forEach(setupFilterWrapper);
2877
2878 // Single event listener for closing dropdowns on outside click
2879 document.addEventListener('click', (e) => {
2880 document
2881 .querySelectorAll('.sp-smart-post-live-filter-dropdown.open')
2882 .forEach((dropdown) => {
2883 const btn = dropdown.previousElementSibling;
2884 if (
2885 !dropdown.contains(e.target) &&
2886 (!btn || !btn.contains(e.target))
2887 ) {
2888 dropdown.classList.remove('open');
2889 }
2890 });
2891 });
2892 //---------------------------------------
2893 /**
2894 * Utility: Wait until all images inside an element are loaded before running a callback.
2895 *
2896 * @param {HTMLElement} el - The container element to monitor.
2897 * @param {Function} callback - Function to call once all images are loaded.
2898 */
2899 const imagesLoaded = (el, callback) => {
2900 const images = el.querySelectorAll('img');
2901 let loadedCount = 0;
2902
2903 // No images? Run callback immediately
2904 if (images.length === 0) {
2905 return callback();
2906 }
2907
2908 images.forEach((img) => {
2909 if (img.complete) {
2910 loadedCount++;
2911 if (loadedCount === images.length) {
2912 callback();
2913 }
2914 } else {
2915 img.onload = img.onerror = () => {
2916 loadedCount++;
2917 if (loadedCount === images.length) {
2918 callback();
2919 }
2920 };
2921 }
2922 });
2923 };
2924
2925 /**
2926 * Utility: Get the unique block ID from an element's closest wrapper.
2927 *
2928 * @param {HTMLElement} child - A child element inside the block.
2929 * @return {string|null} - Block ID or null.
2930 */
2931 const getBlockIdFromChild = (child) => {
2932 const wrapper = child.closest('.sp-smart-post-wrapper');
2933 return wrapper ? wrapper.id : null;
2934 };
2935
2936 /**
2937 * Equal height logic for carousel items (Swiper cards).
2938 */
2939 const carouselContainers = document.querySelectorAll(
2940 '.sp-smart-post-swiper.sp-equal-height-wrapper'
2941 );
2942
2943 carouselContainers.forEach((carousel) => {
2944 const containerId = getBlockIdFromChild(carousel);
2945 const equalHeightEnabled = carousel.dataset.equalHeight == '1';
2946 let container = document.getElementById(containerId);
2947
2948 // Check inside iframe (used in editors like block preview)
2949 if (!container) {
2950 const iframe = document.querySelector('iframe');
2951 const iframeDoc = iframe?.contentWindow?.document;
2952 container = iframeDoc?.getElementById(containerId) || null;
2953 }
2954
2955 if (container) {
2956 const cards = container.querySelectorAll('.sp-smart-post-card');
2957
2958 const applyEqualHeight = () => {
2959 if (!cards.length) {
2960 return;
2961 }
2962
2963 if (equalHeightEnabled) {
2964 // Calculate max height
2965 let maxHeight = 0;
2966 cards.forEach((card) => {
2967 card.style.height = 'auto'; // reset first
2968 const height = card.offsetHeight;
2969 if (height > maxHeight) {
2970 maxHeight = height;
2971 }
2972 });
2973 // Apply max height to all cards
2974 cards.forEach((card) => {
2975 card.style.height = `${maxHeight}px`;
2976 });
2977 } else {
2978 // Reset to auto
2979 cards.forEach((card) => {
2980 card.style.height = 'auto';
2981 });
2982 }
2983 };
2984 // Apply after all images load
2985 imagesLoaded(container, applyEqualHeight);
2986 }
2987 });
2988 // Update selection count field.
2989 update_selection_field_count();
2990
2991 const containers = document.querySelectorAll('.sps-live-filter-layout-two');
2992
2993 containers.forEach((container) => {
2994 const style = container.dataset.style;
2995 let currentPage = parseInt(container.dataset.currentPage);
2996 let isDropdownOpen = container.dataset.isDropdownOpen === 'true';
2997 const totalPages = parseInt(container.dataset.totalPages);
2998
2999 const dropdown = container.querySelector('.sps-live-filter-dropdown');
3000 const dropdownList = container.querySelector(
3001 '.sps-live-filter-dropdown-menu'
3002 );
3003 if (dropdownList && dropdownList?.children.length === 0) {
3004 dropdown.style.display = 'none';
3005 }
3006
3007 // State management functions
3008 function updateState() {
3009 // container.dataset.activeItem = activeItem;
3010 container.dataset.currentPage = currentPage;
3011 container.dataset.isDropdownOpen = isDropdownOpen;
3012 }
3013
3014 function toggleDropdown() {
3015 isDropdownOpen = !isDropdownOpen;
3016 updateState();
3017 updateUI();
3018 }
3019
3020 function closeDropdown() {
3021 isDropdownOpen = false;
3022 updateState();
3023 updateUI();
3024 }
3025
3026 function nextPage() {
3027 if (currentPage < totalPages - 1) {
3028 currentPage++;
3029 updateState();
3030 }
3031 }
3032
3033 function prevPage() {
3034 if (currentPage > 0) {
3035 currentPage--;
3036 updateState();
3037 // updatePage();
3038 }
3039 }
3040
3041 // UI update function
3042 function updateUI() {
3043 // Update dropdown visibility
3044 if (dropdown) {
3045 if (isDropdownOpen) {
3046 dropdown.classList.add('active');
3047 } else {
3048 dropdown.classList.remove('active');
3049 }
3050 }
3051 }
3052
3053 // Event handlers - using event delegation
3054 container.addEventListener('click', function (e) {
3055 // Handle term clicks (including dynamically created ones)
3056 if (
3057 e.target.closest('.sps-live-filter-nav-link') &&
3058 !e.target.closest('.sps-live-filter-dropdown')
3059 ) {
3060 e.preventDefault();
3061 const id = e.target.closest('.sps-live-filter-nav-link').dataset
3062 .id;
3063 // setActiveItem(id);
3064 }
3065
3066 // Handle dropdown toggle
3067 if (
3068 e.target.closest(
3069 '.sps-live-filter-dropdown .sps-live-filter-nav-link'
3070 )
3071 ) {
3072 e.preventDefault();
3073 if (style !== 'navigation') {
3074 toggleDropdown();
3075 }
3076 }
3077
3078 // Handle dropdown item clicks
3079 if (e.target.closest('.sps-live-filter-dropdown-item')) {
3080 e.preventDefault();
3081 const id = e.target.closest('.sps-live-filter-dropdown-item')
3082 .dataset.id;
3083 // setActiveItem(id);
3084 }
3085
3086 // Handle pagination clicks.
3087 if (e.target.closest('.sps-pagination-prev')) {
3088 e.preventDefault();
3089 prevPage();
3090 }
3091
3092 if (e.target.closest('.sps-pagination-next')) {
3093 e.preventDefault();
3094 nextPage();
3095 }
3096 });
3097
3098 // Close dropdown when clicking outside.
3099 document.addEventListener('click', function (e) {
3100 if (!container.contains(e.target)) {
3101 closeDropdown();
3102 }
3103 });
3104 });
3105
3106 // Initialize smart post features on dynamically loaded content
3107 wrapLines();
3108 }
3109
3110 // Expose init function to window object so it can be called from other files
3111 window.init = init;
3112
3113 /**
3114 * Initializes or updates all smart filter dropdowns on the page.
3115 */
3116 function update_selection_field_count() {
3117 document
3118 .querySelectorAll('.sp-smart-post-live-filter-wrapper')
3119 .forEach((wrapper) => {
3120 const selectEl = wrapper.querySelector('select');
3121 const dropdownUl = wrapper.querySelector(
3122 '.sp-smart-post-live-filter-dropdown, .sp-smart-post-live-filter-button, .sp-smart-post-live-filter-button-date'
3123 );
3124 const moreButton = wrapper.querySelector(
3125 '.sps-live-filter-dropdown-menu'
3126 );
3127 buildSmartFilterDropdown(selectEl, dropdownUl, moreButton);
3128 });
3129 }
3130
3131 function buildSmartFilterDropdown(selectEl, dropdownUl, moreButton) {
3132 if (!selectEl || !dropdownUl) return;
3133
3134 // Clear existing dropdown items
3135 dropdownUl.innerHTML = '';
3136 if (moreButton) moreButton.innerHTML = '';
3137
3138 const options = Array.from(selectEl.options);
3139
3140 let attributes = {};
3141 try {
3142 const dataAtt = selectEl.dataset.att;
3143 attributes =
3144 dataAtt && dataAtt !== 'undefined' ? JSON.parse(dataAtt) : {};
3145 } catch (e) {
3146 console.warn('Invalid JSON in data-att:', e);
3147 attributes = {};
3148 }
3149 const { taxonomyLimit, taxonomyStyle, filterType, uniqueId } = attributes;
3150
3151 let device = 'Desktop';
3152
3153 if (window.innerWidth <= 781) device = 'Mobile';
3154 else if (window.innerWidth <= 1024) device = 'Tablet';
3155
3156 // Safely access taxonomyLimit.device
3157 const limit =
3158 taxonomyLimit && taxonomyLimit.device
3159 ? taxonomyLimit.device[device] || 3
3160 : 3;
3161
3162 if (taxonomyStyle === 'navigation') {
3163 // Initialize pagination state
3164 let currentPage = parseInt(dropdownUl.dataset.currentPage) || 1;
3165 const totalPages = Math.ceil(options.length / limit);
3166
3167 // Ensure current page is within valid range
3168 currentPage = Math.max(1, Math.min(currentPage, totalPages));
3169 dropdownUl.dataset.currentPage = currentPage;
3170
3171 // Calculate current page items
3172 const startIndex = (currentPage - 1) * limit;
3173 const endIndex = startIndex + limit;
3174 const currentOptions = options.slice(startIndex, endIndex);
3175
3176 // Add current page options to dropdown
3177 currentOptions.forEach((option) => {
3178 const value = option.value;
3179 if (value === undefined) return;
3180
3181 const label =
3182 option.getAttribute('data-label') || option.textContent.trim();
3183 const count = option.getAttribute('data-default-count') || '';
3184 const isSelected = option.selected ?? false;
3185 const isDisabled = parseInt(count) === 0;
3186
3187 const li = document.createElement('li');
3188 const a = document.createElement('a');
3189 li.classList.add('sps-live-filter-nav-item');
3190 a.classList.add('sps-live-filter-nav-link');
3191
3192 a.href = '#';
3193 a.setAttribute('data-value', value);
3194 a.setAttribute('data-label', label);
3195 a.textContent = `${label}${count ? ` (${count})` : ''}`;
3196
3197 // if (!count && selectEl.id !== "filter-sort") {
3198 // a.classList.add("active");
3199 // }
3200
3201 li.appendChild(a);
3202 if (label) {
3203 dropdownUl.appendChild(li);
3204 }
3205 });
3206
3207 // Create pagination controls
3208 const paginationContainer = document.createElement('div');
3209 paginationContainer.className = 'pagination-controls';
3210
3211 // const container = document.querySelector(".sps-live-filter-layout-two");
3212 const container = document.querySelector(`#${uniqueId}`);
3213 const prev = container.querySelector('.sps-pagination-prev');
3214 const next = container.querySelector('.sps-pagination-next');
3215
3216 prev.addEventListener('click', () => {
3217 dropdownUl.dataset.currentPage = currentPage - 1;
3218 buildSmartFilterDropdown(selectEl, dropdownUl, moreButton);
3219 });
3220
3221 // Page indicator
3222 const pageIndicator = document.createElement('span');
3223 pageIndicator.className = 'pagination-info';
3224 pageIndicator.textContent = `Page ${currentPage} of ${totalPages}`;
3225
3226 next.addEventListener('click', () => {
3227 dropdownUl.dataset.currentPage = currentPage + 1;
3228 buildSmartFilterDropdown(selectEl, dropdownUl, moreButton);
3229 });
3230 } else if (moreButton) {
3231 // Non-navigation style with more button
3232 const optionsOne = options.slice(0, limit);
3233 const optionsTwo = options.slice(limit);
3234
3235 // Add first set of options to dropdown
3236
3237 if (filterType !== 'dropdown') {
3238 optionsOne.forEach((option) => {
3239 const value = option.value;
3240 if (value === undefined) return;
3241
3242 const label =
3243 option.getAttribute('data-label') ||
3244 option.textContent.trim();
3245 const count = option.getAttribute('data-default-count') || '';
3246 const li = document.createElement('li');
3247 const a = document.createElement('a');
3248 li.classList.add('sps-live-filter-nav-item');
3249 a.classList.add('sps-live-filter-nav-link');
3250 if (label === 'All') {
3251 a.classList.add('active');
3252 }
3253
3254 a.href = '#';
3255 a.setAttribute('data-value', value);
3256 a.setAttribute('data-label', label);
3257 a.textContent = `${label}${count ? ` (${count})` : ''}`;
3258
3259 li.appendChild(a);
3260 if (label) {
3261 dropdownUl.appendChild(li);
3262 }
3263 });
3264 }
3265
3266 // Add remaining options to more button
3267 Array.from(filterType === 'dropdown' ? options : optionsTwo).forEach(
3268 (option) => {
3269 const value = option.value;
3270 if (value === undefined) return;
3271 const label =
3272 option.getAttribute('data-label') ||
3273 option.textContent.trim();
3274
3275 const a = document.createElement('a');
3276 a.classList.add('sps-live-filter-nav-link');
3277 a.href = '#';
3278 a.setAttribute('data-value', value);
3279 a.setAttribute('data-label', label);
3280 a.textContent = label;
3281
3282 moreButton.appendChild(a);
3283 }
3284 );
3285 } else {
3286 // No more button - add all options to dropdown
3287 options.forEach((option) => {
3288 const value = option.value;
3289 if (value === undefined) return;
3290
3291 const label =
3292 option.getAttribute('data-label') || option.textContent.trim();
3293 const count = option.getAttribute('data-default-count') || '';
3294
3295 const li = document.createElement('li');
3296 const a = document.createElement('a');
3297 li.classList.add('sps-live-filter-nav-item');
3298 a.classList.add('sps-live-filter-nav-link');
3299
3300 a.href = '#';
3301 a.setAttribute('data-value', value);
3302 a.setAttribute('data-label', label);
3303 a.textContent = `${label}${count ? ` (${count})` : ''}`;
3304
3305 if (!count && selectEl.id !== 'filter-sort') {
3306 a.classList.add('active');
3307 }
3308
3309 li.appendChild(a);
3310 dropdownUl.appendChild(li);
3311 });
3312 }
3313 }
3314
3315 //................................ second layout part 2...................................................................
3316
3317 /**
3318 * Updates all custom dropdowns and their labels based on selected values.
3319 *
3320 * @param {HTMLElement} blockParent - The parent block element containing filter wrappers.
3321 */
3322 function updateAllSmartPostFilterCounts(blockParent) {
3323 const wrappers = blockParent.querySelectorAll(
3324 '.sp-smart-post-live-filter-wrapper'
3325 );
3326
3327 wrappers.forEach((wrapper) => {
3328 const select = wrapper.querySelector('select');
3329 const dropdown = wrapper.querySelector(
3330 '.sp-smart-post-live-filter-dropdown, .sp-smart-post-live-filter-button, .sp-smart-post-live-filter-button-date'
3331 );
3332 const selectedButton = wrapper.querySelector(
3333 '.sp-smart-post-live-filter-btn span'
3334 );
3335 const this_filter = wrapper.querySelector('.sp-smart-post-live-filter');
3336 if (select.id === 'filter-sort') {
3337 // Build map: value → { label, count }
3338 const optionMap = Array.from(select.options).reduce(
3339 (map, option) => {
3340 const value = option.value;
3341 const label =
3342 option.getAttribute('data-label') ||
3343 option.textContent.trim();
3344 // Extract count from text, fallback to 0
3345 const isSelected = option.selected ?? false;
3346 map[value] = { label, isSelected };
3347 return map;
3348 },
3349 {}
3350 );
3351 dropdown.querySelectorAll('a').forEach((a) => {
3352 const value = a.getAttribute('data-value');
3353 const { label, isSelected } = optionMap[value];
3354 if (isSelected) {
3355 dropdown
3356 .querySelectorAll('a')
3357 .forEach((el) => el.classList.remove('active'));
3358 a.classList.add('active');
3359 }
3360 });
3361 // Update selected button label
3362 if (select.value && selectedButton) {
3363 const { label, count } = optionMap[select.value] || {};
3364 if (label !== undefined) {
3365 selectedButton.textContent = `${label}`;
3366 }
3367 }
3368 }
3369
3370 if (!select || !dropdown || select.id === 'filter-sort') {
3371 return;
3372 }
3373 const show_count = this_filter?.getAttribute('data-show-count');
3374 // Build map: value → { label, count }
3375 const optionMap = Array.from(select.options).reduce((map, option) => {
3376 const value = option.value;
3377 const label =
3378 option.getAttribute('data-label') || option.textContent.trim();
3379 // Extract count from text, fallback to 0
3380 const countMatch = option.textContent.trim().match(/\((\d+)\)/);
3381 const count = countMatch ? countMatch[1] : '0';
3382 const isSelected = option.selected ?? false;
3383 map[value] = { label, count, isSelected };
3384 return map;
3385 }, {});
3386
3387 // Update selected button label
3388 if (select.value && selectedButton) {
3389 const { label, count } = optionMap[select.value] || {};
3390 const count_text = show_count ? `(${count})` : '';
3391 if (label !== undefined) {
3392 selectedButton.textContent = `${label} ${count_text}`;
3393 }
3394 }
3395
3396 // Update dropdown items with latest labels and counts
3397 dropdown.querySelectorAll('a').forEach((a) => {
3398 const value = a.getAttribute('data-value');
3399 if (!value || !optionMap[value]) {
3400 return;
3401 }
3402
3403 const { label, count, isSelected } = optionMap[value];
3404
3405 a.setAttribute('data-label', label);
3406 a.setAttribute('data-default-count', count);
3407 const count_text = show_count ? `(${count})` : '';
3408 a.textContent = `${label} ${count_text}`;
3409 if (parseInt(count) === 0) {
3410 a.classList.add('disabled');
3411 a.setAttribute('aria-disabled', 'true');
3412 } else {
3413 a.classList.remove('disabled');
3414 a.removeAttribute('aria-disabled');
3415 if (isSelected) {
3416 dropdown
3417 .querySelectorAll('a')
3418 .forEach((el) => el.classList.remove('active'));
3419 a.classList.add('active');
3420 }
3421 }
3422 });
3423 });
3424 }
3425
3426 //------------------------title effect js--------
3427 function wrapLines() {
3428 const selector = `
3429 .sp-smart-post-grid-two .sp-smart-post-title,
3430 .sp-smart-post-grid-three .sp-smart-post-title,
3431 .sp-smart-post-carousel-two .sp-smart-post-title,
3432 .sp-smart-post-slider .sp-smart-post-title,
3433 .sp-smart-post-slider-two .sp-smart-post-title,
3434 .sp-smart-post-thumbnail-slider .sp-smart-post-title,
3435 .sp-smart-post-thumbnail-slide-two .sp-smart-post-title,
3436 .sp-smart-post-grid-four .sp-smart-post-title,
3437 .sp-smart-post-grid-five .sp-smart-post-title,
3438 .sp-smart-post-grid-six .sp-smart-post-title,
3439 .sp-smart-post-timeline-two .sp-smart-post-title`;
3440
3441 document.querySelectorAll(selector).forEach((title) => {
3442 if (title.dataset.processed === 'true') return;
3443
3444 const textElement = title.querySelector('.sp-smart-post-title-text');
3445 if (!textElement) return;
3446
3447 // Get trimmed text content only from title-text span
3448 const text = textElement.textContent.trim();
3449 if (!text) return;
3450
3451 const temp = document.createElement('div');
3452 const style = window.getComputedStyle(textElement);
3453 Object.assign(temp.style, {
3454 position: 'absolute',
3455 visibility: 'hidden',
3456 whiteSpace: 'nowrap',
3457 fontSize: style.fontSize,
3458 fontFamily: style.fontFamily,
3459 fontWeight: style.fontWeight,
3460 });
3461 document.body.appendChild(temp);
3462
3463 const containerWidth = textElement.offsetWidth || title.offsetWidth;
3464 const words = text.split(' ');
3465 const lines = [];
3466 let currentLine = '';
3467
3468 words.forEach((word) => {
3469 const testLine = currentLine ? `${currentLine} ${word}` : word;
3470 temp.textContent = testLine;
3471 if (temp.offsetWidth > containerWidth && currentLine) {
3472 lines.push(currentLine);
3473 currentLine = word;
3474 } else {
3475 currentLine = testLine;
3476 }
3477 });
3478 if (currentLine) lines.push(currentLine);
3479 document.body.removeChild(temp);
3480
3481 textElement.innerHTML = ''; // clear only text span
3482
3483 lines.forEach((line, index) => {
3484 const span = document.createElement('span');
3485 span.className = 'line';
3486 span.textContent = line;
3487 textElement.appendChild(span);
3488 if (index < lines.length - 1) {
3489 textElement.appendChild(document.createElement('br'));
3490 }
3491 });
3492
3493 title.dataset.processed = 'true';
3494 });
3495 }