| @@ -7,10 +7,48 @@ | ||
| 7 | 7 | 'use strict'; |
| 8 | 8 | |
| 9 | 9 | $(window).on("elementor/frontend/init", () => { |
| 10 | 10 | elementorFrontend.hooks.addAction("frontend/element_ready/king-addons-dynamic-posts-grid.default", ($scope) => { |
| 11 | + // Check if this is PRO mode by looking at widget-mode data attribute | |
| 12 | + const $wrapper = $scope.find('.king-addons-dpg-wrapper'); | |
| 11 | 13 | |
| 14 | + // Prevent double initialization | |
| 15 | + if ($wrapper.data('king-addons-initialized')) { | |
| 16 | + return; | |
| 17 | + } | |
| 18 | + $wrapper.data('king-addons-initialized', true); | |
| 19 | + | |
| 20 | + const widgetMode = $wrapper.data('widget-mode'); | |
| 21 | + const isPro = widgetMode === 'custom_cpt'; | |
| 22 | + | |
| 12 | 23 | const gridHandler = { |
| 24 | + // Sanitize HTML using DOMPurify for XSS protection | |
| 25 | + sanitizeHtml(value) { | |
| 26 | + if (typeof DOMPurify !== 'undefined') { | |
| 27 | + return DOMPurify.sanitize(String(value), {ALLOWED_TAGS: [], ALLOWED_ATTR: []}); | |
| 28 | + } | |
| 29 | + // Fallback | |
| 30 | + return String(value) | |
| 31 | + .replace(/&/g, "&") | |
| 32 | + .replace(/</g, "<") | |
| 33 | + .replace(/>/g, ">") | |
| 34 | + .replace(/"/g, """) | |
| 35 | + .replace(/'/g, "'"); | |
| 36 | + }, | |
| 37 | + | |
| 38 | + normalizeSafeUrl(rawUrl, allowedProtocols = ["http:", "https:"]) { | |
| 39 | + if (!rawUrl || typeof rawUrl !== "string") return null; | |
| 40 | + try { | |
| 41 | + const url = new URL(rawUrl, window.location.href); | |
| 42 | + if (allowedProtocols.includes(url.protocol)) { | |
| 43 | + return url.href; | |
| 44 | + } | |
| 45 | + } catch (e) { | |
| 46 | + // ignore | |
| 47 | + } | |
| 48 | + return null; | |
| 49 | + }, | |
| 50 | + | |
| 13 | 51 | init() { |
| 14 | 52 | this.wrapper = $scope.find('.king-addons-dpg-wrapper'); |
| 15 | 53 | this.grid = $scope.find('.king-addons-dpg-grid'); |
| 16 | 54 | this.filterBar = $scope.find('.king-addons-dpg-filter-bar'); |
| @@ -29,8 +67,17 @@ | ||
| 29 | 67 | this.currentSearch = ''; |
| 30 | 68 | |
| 31 | 69 | this.bindEvents(); |
| 32 | 70 | this.initIsotope(); |
| 71 | + | |
| 72 | + // Prepare CPT icon map for client-side application after AJAX | |
| 73 | + this.cptIcons = {}; | |
| 74 | + try { | |
| 75 | + const raw = this.settings.cptIconsRaw || ''; | |
| 76 | + this.cptIcons = raw ? JSON.parse(raw) : {}; | |
| 77 | + } catch (e) { | |
| 78 | + this.cptIcons = {}; | |
| 79 | + } | |
| 33 | 80 | }, |
| 34 | 81 | |
| 35 | 82 | getSettings() { |
| 36 | 83 | return { |
| @@ -40,9 +87,11 @@ | ||
| 40 | 87 | orderby: this.wrapper.data('orderby'), |
| 41 | 88 | order: this.wrapper.data('order'), |
| 42 | 89 | filterTaxonomy: this.wrapper.data('filter-taxonomy'), |
| 43 | 90 | showExcerpt: this.wrapper.data('show-excerpt'), |
| 44 | - cardClickable: this.wrapper.data('card-clickable') | |
| 91 | + cardClickable: this.wrapper.data('card-clickable'), | |
| 92 | + cptActionsRaw: this.wrapper.attr('data-cpt-actions') || '', | |
| 93 | + cptIconsRaw: this.wrapper.attr('data-cpt-icons') || '' | |
| 45 | 94 | }; |
| 46 | 95 | }, |
| 47 | 96 | |
| 48 | 97 | bindEvents() { |
| @@ -81,8 +130,11 @@ | ||
| 81 | 130 | }); |
| 82 | 131 | |
| 83 | 132 | // Card click functionality |
| 84 | 133 | this.bindCardClickEvents(); |
| 134 | + | |
| 135 | + // Action button events | |
| 136 | + this.bindActionButtonEvents(); | |
| 85 | 137 | }, |
| 86 | 138 | |
| 87 | 139 | bindCardClickEvents() { |
| 88 | 140 | // Only enable card clicking if setting is enabled and not in Elementor editor |
| @@ -98,10 +150,12 @@ | ||
| 98 | 150 | const postLink = $(e.currentTarget).find('.king-addons-dpg-title a'); |
| 99 | 151 | if (postLink.length > 0) { |
| 100 | 152 | const postUrl = postLink.attr('href'); |
| 101 | 153 | if (postUrl) { |
| 102 | - // Navigate to post | |
| 103 | - window.location.href = postUrl; | |
| 154 | + const safePostUrl = this.normalizeSafeUrl(postUrl); | |
| 155 | + if (safePostUrl) { | |
| 156 | + window.location.href = safePostUrl; | |
| 157 | + } | |
| 104 | 158 | } |
| 105 | 159 | } |
| 106 | 160 | }); |
| 107 | 161 | |
| @@ -109,8 +163,225 @@ | ||
| 109 | 163 | this.wrapper.addClass('king-addons-dpg-cards-clickable'); |
| 110 | 164 | } |
| 111 | 165 | }, |
| 112 | 166 | |
| 167 | + bindActionButtonEvents() { | |
| 168 | + // Prevent multiple event bindings | |
| 169 | + this.wrapper.off('click', '.king-addons-dpg-action-btn'); | |
| 170 | + | |
| 171 | + // Use event delegation for dynamically loaded content | |
| 172 | + this.wrapper.on('click', '.king-addons-dpg-action-btn', (e) => { | |
| 173 | + e.preventDefault(); | |
| 174 | + e.stopPropagation(); | |
| 175 | + | |
| 176 | + const $button = $(e.target).closest('.king-addons-dpg-action-btn'); | |
| 177 | + | |
| 178 | + // Prevent double clicks and global lightbox blocking | |
| 179 | + if ($button.data('clicking') || $('body').data('lightbox-opening')) { | |
| 180 | + return; | |
| 181 | + } | |
| 182 | + | |
| 183 | + $button.data('clicking', true); | |
| 184 | + $('body').data('lightbox-opening', true); | |
| 185 | + | |
| 186 | + setTimeout(() => { | |
| 187 | + $button.removeData('clicking'); | |
| 188 | + $('body').removeData('lightbox-opening'); | |
| 189 | + }, 1500); | |
| 190 | + | |
| 191 | + const action = $button.data('action'); | |
| 192 | + const url = $button.data('url'); | |
| 193 | + const title = $button.data('title') || ''; | |
| 194 | + | |
| 195 | + if (!url) { | |
| 196 | + // console.warn('No URL provided for action button'); | |
| 197 | + return; | |
| 198 | + } | |
| 199 | + | |
| 200 | + switch (action) { | |
| 201 | + case 'lightbox_image': | |
| 202 | + this.openImageLightbox(url, title); | |
| 203 | + break; | |
| 204 | + case 'lightbox_video': | |
| 205 | + this.openVideoLightbox(url, title); | |
| 206 | + break; | |
| 207 | + case 'new_tab': | |
| 208 | + default: | |
| 209 | + window.open(url, '_blank'); | |
| 210 | + break; | |
| 211 | + } | |
| 212 | + }); | |
| 213 | + }, | |
| 214 | + | |
| 215 | + openImageLightbox(url, title) { | |
| 216 | + const safeUrl = this.normalizeSafeUrl(url); | |
| 217 | + if (!safeUrl) { | |
| 218 | + return; | |
| 219 | + } | |
| 220 | + | |
| 221 | + // Prevent multiple lightboxes from opening simultaneously | |
| 222 | + if ($('.lg-backdrop, .lg-outer, .king-addons-dpg-lightbox-temp, [data-lg-uid]').length > 0) { | |
| 223 | + return; | |
| 224 | + } | |
| 225 | + | |
| 226 | + // Check if lightGallery is already running | |
| 227 | + if (window.lgCurrentInstance) { | |
| 228 | + return; | |
| 229 | + } | |
| 230 | + | |
| 231 | + // Remove any orphaned containers first | |
| 232 | + $('.king-addons-dpg-lightbox-temp').remove(); | |
| 233 | + | |
| 234 | + // Create array with single image item for LightGallery | |
| 235 | + const galleryItems = [{ | |
| 236 | + src: safeUrl, | |
| 237 | + subHtml: title ? '<div>' + this.sanitizeHtml(title) + '</div>' : '' | |
| 238 | + }]; | |
| 239 | + | |
| 240 | + // Initialize LightGallery directly with dynamic gallery | |
| 241 | + if (typeof $.fn.lightGallery !== 'undefined') { | |
| 242 | + // Create a temporary div just for gallery initialization | |
| 243 | + const $tempDiv = $('<div style="display:none;"></div>'); | |
| 244 | + $('body').append($tempDiv); | |
| 245 | + | |
| 246 | + $tempDiv.lightGallery({ | |
| 247 | + dynamic: true, | |
| 248 | + dynamicEl: galleryItems, | |
| 249 | + download: false, | |
| 250 | + counter: false, | |
| 251 | + zoom: true, | |
| 252 | + fullScreen: true, | |
| 253 | + controls: true, | |
| 254 | + thumbnail: false, | |
| 255 | + closable: true, | |
| 256 | + escKey: true, | |
| 257 | + keyPress: true | |
| 258 | + }); | |
| 259 | + | |
| 260 | + // Clean up on close | |
| 261 | + $tempDiv.on('onCloseAfter.lg', function() { | |
| 262 | + window.lgCurrentInstance = false; | |
| 263 | + setTimeout(() => { | |
| 264 | + $tempDiv.remove(); | |
| 265 | + }, 100); | |
| 266 | + }); | |
| 267 | + } else { | |
| 268 | + // console.error('LightGallery not available'); | |
| 269 | + window.open(safeUrl, '_blank'); | |
| 270 | + } | |
| 271 | + }, | |
| 272 | + | |
| 273 | + openVideoLightbox(url, title) { | |
| 274 | + // Check if LightGallery is available | |
| 275 | + if (typeof $.fn.lightGallery === 'undefined') { | |
| 276 | + // console.warn('LightGallery not loaded, opening video in new tab'); | |
| 277 | + const safeUrl = this.normalizeSafeUrl(url); | |
| 278 | + if (safeUrl) { | |
| 279 | + window.open(safeUrl, '_blank'); | |
| 280 | + } | |
| 281 | + return; | |
| 282 | + } | |
| 283 | + | |
| 284 | + // For old LightGallery v1.6.12, let's create a manual video popup | |
| 285 | + this.createYouTubePopup(url, title); | |
| 286 | + }, | |
| 287 | + | |
| 288 | + createYouTubePopup(url, title) { | |
| 289 | + const safeUrl = this.normalizeSafeUrl(url); | |
| 290 | + if (!safeUrl) { | |
| 291 | + return; | |
| 292 | + } | |
| 293 | + | |
| 294 | + // Process YouTube URL to get video ID | |
| 295 | + const videoId = this.getYouTubeVideoId(safeUrl); | |
| 296 | + if (!videoId) { | |
| 297 | + // console.warn('Invalid YouTube URL'); | |
| 298 | + window.open(safeUrl, '_blank'); | |
| 299 | + return; | |
| 300 | + } | |
| 301 | + | |
| 302 | + // Create manual video popup similar to LightGallery structure | |
| 303 | + const popupHtml = ` | |
| 304 | + <div class="king-addons-video-popup" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.9); z-index: 9999; display: flex; align-items: center; justify-content: center;"> | |
| 305 | + <div class="king-addons-video-container" style="position: relative; width: 90%; max-width: 1200px; max-height: 90%;"> | |
| 306 | + <button class="king-addons-video-close" style="position: absolute; top: -40px; right: 0; background: none; border: none; color: white; font-size: 30px; cursor: pointer; z-index: 10000;">×</button> | |
| 307 | + <div class="king-addons-video-wrapper" style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"> | |
| 308 | + <iframe | |
| 309 | + src="https://www.youtube.com/embed/${videoId}?autoplay=1&modestbranding=1&rel=0&showinfo=0&controls=1" | |
| 310 | + style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0;" | |
| 311 | + allowfullscreen | |
| 312 | + allow="autoplay; encrypted-media"> | |
| 313 | + </iframe> | |
| 314 | + </div> | |
| 315 | + ${title ? `<div style="color: white; text-align: center; margin-top: 10px;">${title}</div>` : ''} | |
| 316 | + </div> | |
| 317 | + </div> | |
| 318 | + `; | |
| 319 | + | |
| 320 | + const $popup = $(popupHtml); | |
| 321 | + $('body').append($popup); | |
| 322 | + | |
| 323 | + // Handle close events | |
| 324 | + $popup.find('.king-addons-video-close').on('click', () => { | |
| 325 | + this.closeVideoPopup($popup); | |
| 326 | + }); | |
| 327 | + | |
| 328 | + $popup.on('click', (e) => { | |
| 329 | + if (e.target === $popup[0]) { | |
| 330 | + this.closeVideoPopup($popup); | |
| 331 | + } | |
| 332 | + }); | |
| 333 | + | |
| 334 | + // Handle ESC key | |
| 335 | + $(document).on('keydown.video-popup', (e) => { | |
| 336 | + if (e.keyCode === 27) { | |
| 337 | + this.closeVideoPopup($popup); | |
| 338 | + } | |
| 339 | + }); | |
| 340 | + | |
| 341 | + // Animate in | |
| 342 | + $popup.css('opacity', 0).animate({opacity: 1}, 300); | |
| 343 | + }, | |
| 344 | + | |
| 345 | + getYouTubeVideoId(url) { | |
| 346 | + // Extract video ID from various YouTube URL formats | |
| 347 | + const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/; | |
| 348 | + const match = url.match(regExp); | |
| 349 | + return (match && match[7].length === 11) ? match[7] : null; | |
| 350 | + }, | |
| 351 | + | |
| 352 | + closeVideoPopup($popup) { | |
| 353 | + // Stop video by removing iframe | |
| 354 | + $popup.find('iframe').attr('src', ''); | |
| 355 | + | |
| 356 | + // Remove event listeners | |
| 357 | + $(document).off('keydown.video-popup'); | |
| 358 | + | |
| 359 | + // Animate out and remove | |
| 360 | + $popup.animate({opacity: 0}, 300, function() { | |
| 361 | + $popup.remove(); | |
| 362 | + }); | |
| 363 | + }, | |
| 364 | + | |
| 365 | + processYouTubeUrl(url) { | |
| 366 | + // Handle different YouTube URL formats | |
| 367 | + if (url.includes('youtube.com/watch?v=')) { | |
| 368 | + // Already in correct format | |
| 369 | + return url; | |
| 370 | + } else if (url.includes('youtu.be/')) { | |
| 371 | + // Convert youtu.be/VIDEO_ID to youtube.com/watch?v=VIDEO_ID | |
| 372 | + const videoId = url.split('youtu.be/')[1].split('?')[0].split('&')[0]; | |
| 373 | + return `https://www.youtube.com/watch?v=${videoId}`; | |
| 374 | + } else if (url.includes('youtube.com/embed/')) { | |
| 375 | + // Convert youtube.com/embed/VIDEO_ID to youtube.com/watch?v=VIDEO_ID | |
| 376 | + const videoId = url.split('/embed/')[1].split('?')[0].split('&')[0]; | |
| 377 | + return `https://www.youtube.com/watch?v=${videoId}`; | |
| 378 | + } | |
| 379 | + | |
| 380 | + // Return original URL if not YouTube or unknown format | |
| 381 | + return url; | |
| 382 | + }, | |
| 383 | + | |
| 113 | 384 | initIsotope() { |
| 114 | 385 | // Skip Isotope initialization in Elementor editor |
| 115 | 386 | if (elementorFrontend.isEditMode()) { |
| 116 | 387 | return; |
| @@ -147,8 +418,14 @@ | ||
| 147 | 418 | } |
| 148 | 419 | }, |
| 149 | 420 | |
| 150 | 421 | filterAndSearch() { |
| 422 | + // PRO: Use client-side filtering for CPT mode | |
| 423 | + if (widgetMode === 'custom_cpt') { | |
| 424 | + this.filterByPostType(); | |
| 425 | + return; | |
| 426 | + } | |
| 427 | + | |
| 151 | 428 | // Skip AJAX calls in Elementor editor |
| 152 | 429 | if (elementorFrontend.isEditMode()) { |
| 153 | 430 | return; |
| 154 | 431 | } |
| @@ -169,9 +446,10 @@ | ||
| 169 | 446 | filter_taxonomy: this.settings.filterTaxonomy, |
| 170 | 447 | filter_term: this.currentFilter, |
| 171 | 448 | search_query: this.currentSearch, |
| 172 | 449 | page: this.currentPage, |
| 173 | - show_excerpt: this.settings.showExcerpt | |
| 450 | + show_excerpt: this.settings.showExcerpt, | |
| 451 | + cpt_actions: this.settings.cptActionsRaw | |
| 174 | 452 | }; |
| 175 | 453 | |
| 176 | 454 | $.ajax({ |
| 177 | 455 | url: window.KingAddonsDynamicPostsGrid?.ajaxUrl || '/wp-admin/admin-ajax.php', |
| @@ -180,9 +458,9 @@ | ||
| 180 | 458 | success: (response) => { |
| 181 | 459 | this.handleFilterResponse(response); |
| 182 | 460 | }, |
| 183 | 461 | error: (xhr, status, error) => { |
| 184 | - console.error('Dynamic Posts Grid AJAX Error:', error); | |
| 462 | + // console.error('Dynamic Posts Grid AJAX Error:', error); | |
| 185 | 463 | this.hideLoading(); |
| 186 | 464 | this.isLoading = false; |
| 187 | 465 | } |
| 188 | 466 | }); |
| @@ -211,9 +489,10 @@ | ||
| 211 | 489 | filter_taxonomy: this.settings.filterTaxonomy, |
| 212 | 490 | filter_term: this.currentFilter, |
| 213 | 491 | search_query: this.currentSearch, |
| 214 | 492 | page: this.currentPage, |
| 215 | - show_excerpt: this.settings.showExcerpt | |
| 493 | + show_excerpt: this.settings.showExcerpt, | |
| 494 | + cpt_actions: this.settings.cptActionsRaw | |
| 216 | 495 | }; |
| 217 | 496 | |
| 218 | 497 | $.ajax({ |
| 219 | 498 | url: window.KingAddonsDynamicPostsGrid?.ajaxUrl || '/wp-admin/admin-ajax.php', |
| @@ -222,9 +501,9 @@ | ||
| 222 | 501 | success: (response) => { |
| 223 | 502 | this.handleLoadMoreResponse(response); |
| 224 | 503 | }, |
| 225 | 504 | error: (xhr, status, error) => { |
| 226 | - console.error('Dynamic Posts Grid Load More Error:', error); | |
| 505 | + // console.error('Dynamic Posts Grid Load More Error:', error); | |
| 227 | 506 | this.hideLoading(); |
| 228 | 507 | this.isLoading = false; |
| 229 | 508 | this.currentPage--; // Revert page increment on error |
| 230 | 509 | } |
| @@ -292,8 +571,21 @@ | ||
| 292 | 571 | |
| 293 | 572 | // Update pagination |
| 294 | 573 | this.updatePagination(response.data); |
| 295 | 574 | |
| 575 | + // Ensure clicks on links/buttons do not bubble to card-click handler | |
| 576 | + this.wrapper.off('click', '.king-addons-dpg-card a, .king-addons-dpg-card button') | |
| 577 | + .on('click', '.king-addons-dpg-card a, .king-addons-dpg-card button', (e) => { | |
| 578 | + e.stopPropagation(); | |
| 579 | + }); | |
| 580 | + | |
| 581 | + // If CPT mode, re-apply client-side filter (post type + search) | |
| 582 | + if (widgetMode === 'custom_cpt') { | |
| 583 | + // Apply CPT icons to newly added items | |
| 584 | + this.applyCptIcons(newItems); | |
| 585 | + this.filterByPostType(); | |
| 586 | + } | |
| 587 | + | |
| 296 | 588 | } else { |
| 297 | 589 | this.showError(response.data?.message || 'Failed to load more posts'); |
| 298 | 590 | this.currentPage--; // Revert page increment |
| 299 | 591 | } |
| @@ -298,8 +590,35 @@ | ||
| 298 | 590 | this.currentPage--; // Revert page increment |
| 299 | 591 | } |
| 300 | 592 | }, |
| 301 | 593 | |
| 594 | + applyCptIcons($scopeItems) { | |
| 595 | + if (!this.cptIcons || typeof this.cptIcons !== 'object') return; | |
| 596 | + $scopeItems.each((_, el) => { | |
| 597 | + const $card = $(el); | |
| 598 | + const postType = $card.data('post-type'); | |
| 599 | + if (!postType) return; | |
| 600 | + const conf = this.cptIcons[postType]; | |
| 601 | + if (!conf) return; | |
| 602 | + | |
| 603 | + const $iconWrap = $card.find('.king-addons-dpg-icon'); | |
| 604 | + if (conf.icon_type === 'image' && conf.image_url) { | |
| 605 | + const safeImgUrl = this.normalizeSafeUrl(conf.image_url); | |
| 606 | + if (!safeImgUrl) return; | |
| 607 | + $iconWrap.empty().append( | |
| 608 | + $("<img />", { | |
| 609 | + src: safeImgUrl, | |
| 610 | + alt: String(postType || ""), | |
| 611 | + }) | |
| 612 | + ); | |
| 613 | + } else if (conf.icon_class) { | |
| 614 | + $iconWrap.empty().append( | |
| 615 | + $("<i />").addClass(String(conf.icon_class || "")) | |
| 616 | + ); | |
| 617 | + } | |
| 618 | + }); | |
| 619 | + }, | |
| 620 | + | |
| 302 | 621 | updatePagination(data) { |
| 303 | 622 | if (data.current_page >= data.max_pages) { |
| 304 | 623 | this.loadMoreBtn.hide(); |
| 305 | 624 | this.showFinished(data.total_posts, data.current_count); |
| @@ -374,8 +693,42 @@ | ||
| 374 | 693 | clearTimeout(timeout); |
| 375 | 694 | timeout = setTimeout(later, wait); |
| 376 | 695 | if (callNow) func.apply(context, args); |
| 377 | 696 | }; |
| 697 | + }, | |
| 698 | + | |
| 699 | + // PRO Methods | |
| 700 | + filterByPostType() { | |
| 701 | + // Filter posts by post type (for CPT mode) | |
| 702 | + if (widgetMode === 'custom_cpt') { | |
| 703 | + const $cards = this.grid.find('.king-addons-dpg-item'); | |
| 704 | + let $candidate = $cards; | |
| 705 | + | |
| 706 | + // Filter by selected post type first | |
| 707 | + if (this.currentFilter !== '*') { | |
| 708 | + $candidate = $candidate.filter('[data-post-type="' + this.currentFilter + '"]'); | |
| 709 | + } | |
| 710 | + | |
| 711 | + // Then apply keyword search (title text) | |
| 712 | + const query = (this.currentSearch || '').toString().trim().toLowerCase(); | |
| 713 | + if (query.length > 0) { | |
| 714 | + $candidate = $candidate.filter((_, el) => { | |
| 715 | + const $el = $(el); | |
| 716 | + const titleText = $el.find('.king-addons-dpg-title').text().toLowerCase(); | |
| 717 | + const excerptText = $el.find('.king-addons-dpg-excerpt').text().toLowerCase(); | |
| 718 | + return (titleText.indexOf(query) !== -1) || (excerptText.indexOf(query) !== -1); | |
| 719 | + }); | |
| 720 | + } | |
| 721 | + | |
| 722 | + // Show only matching cards | |
| 723 | + $cards.hide(); | |
| 724 | + $candidate.show(); | |
| 725 | + | |
| 726 | + // Re-layout if using Isotope | |
| 727 | + if (this.grid.data('isotopekng')) { | |
| 728 | + this.grid.isotopekng('layout'); | |
| 729 | + } | |
| 730 | + } | |
| 378 | 731 | } |
| 379 | 732 | }; |
| 380 | 733 | |
| 381 | 734 | // Initialize the grid handler |
| @@ -395,7 +748,9 @@ | ||
| 395 | 748 | }, 100); |
| 396 | 749 | }); |
| 397 | 750 | } |
| 398 | 751 | }); |
| 752 | + | |
| 753 | + // PRO version uses the same hook name as Free version, no separate hook needed | |
| 399 | 754 | }); |
| 400 | 755 | |
| 401 | -})(jQuery); | |
| 756 | +})(jQuery); | |