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 14function 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 3111window.init = init; 3112 3113/** 3114 * Initializes or updates all smart filter dropdowns on the page. 3115 */ 3116function 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 3131function 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 */ 3322function 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-------- 3427function 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}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.