PluginProbe
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits / 3.1.2
Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits v3.1.2
3.2.2 3.2.3 3.2.1 3.2.0 3.1.9 3.1.8 3.1.7 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.9 trunk 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.3 1.1.4 1.1.5 All 174 releases
master-addons / inc / admin / templates / includes / classes / assets.php

assets.php in Master Addons for Elementor – Elementor Addons, Widgets, Mega Menu Builder, Popup Builder, Widget Builder & Template Kits 3.1.2, at inc/admin/templates/includes/classes/assets.php

906 lines 30.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace MasterAddons\Inc\Admin\Templates\Includes\Classes;
4
5 use MasterAddons\Inc\Admin\Templates;
6
7 /**
8 * Author Name: Liton Arefin
9 * Author URL: https://jeweltheme.com
10 * Date: 9/8/19
11 */
12
13 if (!defined('ABSPATH')) exit; // No access of directly access
14
15 if (!class_exists(__NAMESPACE__ . '\\Assets')) {
16
17
18 class Assets
19 {
20
21
22 private static $instance = null;
23
24 public function __construct()
25 {
26
27 add_action('elementor/preview/enqueue_styles', array($this, 'enqueue_preview_styles'));
28
29 add_action('elementor/editor/before_enqueue_scripts', array($this, 'editor_scripts'), -1);
30
31 add_action('elementor/editor/after_enqueue_styles', array($this, 'editor_styles'));
32
33 add_action('elementor/editor/footer', array($this, 'load_footer_scripts'));
34 }
35
36
37 public function editor_styles()
38 {
39 wp_enqueue_style('master-editor-only', JLTMA_ASSETS . 'css/admin/editor.css', [], JLTMA_VER);
40 }
41
42
43 public function enqueue_preview_styles()
44 {
45 $css = '
46 .elementor-add-new-section .ma-el-add-section-btn {
47 margin-left: 5px;
48 padding: 0;
49 }
50 .elementor-add-new-section .ma-el-add-section-btn .jltma-editor-icon {
51 background: url(' . esc_url(JLTMA_IMAGE_DIR . 'ma-editor-icon.svg') . ') center center no-repeat;
52 background-size: contain;
53 width: 100%;
54 height: 100%;
55 }
56 ';
57 wp_register_style('master-addons-editor-preview', false, array(), JLTMA_VER);
58 wp_enqueue_style('master-addons-editor-preview');
59 wp_add_inline_style('master-addons-editor-preview', $css);
60 }
61
62
63 public function editor_scripts()
64 {
65 wp_enqueue_script('master-addons-editor-js', JLTMA_ASSETS . 'js/admin/editor.js', array('jquery', 'underscore'), JLTMA_VER, true);
66
67 $button = Templates\master_addons_templates()->config->get('master_addons_templates');
68
69 wp_localize_script(
70 'master-addons-editor-js',
71 'MasterAddonsData',
72 apply_filters(
73 'master-addons-core/assets/editor/localize',
74 array(
75 'master_image_dir' => JLTMA_IMAGE_DIR . 'ma-editor-icon.svg',
76 'MasterAddonsEditorBtn' => $button,
77 'get_templates_nonce' => wp_create_nonce('jltma_get_templates_nonce_action'),
78 'insert_template_nonce' => wp_create_nonce('jltma_insert_templates_nonce_action'),
79 'refresh_cache_nonce' => wp_create_nonce('master_addons_nonce'),
80 'modalRegions' => $this->get_modal_region(),
81 'license' => array(
82 'status' => Templates\master_addons_templates()->config->get('status'),
83 'activateLink' => Templates\master_addons_templates()->config->get('license_page'),
84 'proMessage' => Templates\master_addons_templates()->config->get('pro_message')
85 ),
86 )
87 )
88 );
89 }
90
91
92 public function get_modal_region()
93 {
94
95 return array(
96 'modalHeader' => '.dialog-header',
97 'modalContent' => '.dialog-message',
98 );
99 }
100
101
102 public function load_footer_scripts()
103 {
104
105
106 $scripts = glob(JLTMA_PATH . 'inc/admin/templates/editor/*.php');
107
108 array_map(function ($file) {
109
110 $name = basename($file, '.php');
111
112 ob_start();
113
114 include $file;
115
116 printf('<script type="text/html" id="views-ma-el-%1$s">%2$s</script>', esc_attr( $name ), ob_get_clean()); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- ob_get_clean() returns a PHP template file's output intended for Backbone/Underscore JS template rendering
117 }, $scripts);
118
119 // Add cache refresh functionality for template modal
120 $this->add_cache_refresh_script();
121 }
122
123
124 public function add_cache_refresh_script()
125 {
126 ?>
127 <script type="text/javascript">
128 jQuery(document).ready(function($) {
129 // Global Macy instance
130 var macyInstance = null;
131
132 // Wait for Elementor to be available and add cache refresh functionality
133 function initCacheRefresh() {
134 if (typeof window.elementor === 'undefined') {
135 setTimeout(initCacheRefresh, 100);
136 return;
137 }
138
139 // Initialize Macy.js for template grid
140 initMacyLayout();
141
142 // Add tab switching preloader functionality
143 initTabPreloader();
144
145 // Add template search functionality
146 initTemplateSearch();
147
148 // Initialize filters and keywords
149 initFiltersAndKeywords();
150
151 // Add banner click tracking
152 $(document).on('click', '.ma-el-template-library--banner', function(e) {
153 // Track banner click for analytics (optional)
154 if (typeof gtag !== 'undefined') {
155 gtag('event', 'banner_click', {
156 'event_category': 'template_library',
157 'event_label': 'template_banner',
158 'transport_type': 'beacon'
159 });
160 }
161
162 });
163
164 // Add event handler using delegation for dynamically loaded modal content
165 $(document).on('click', '#ma-el-template-cache-refresh', function(e) {
166 e.preventDefault();
167
168 var $button = $(this);
169 var $icon = $button.find('i');
170
171 // Add updating state
172 $button.addClass('updating');
173 $icon.removeClass('eicon-sync').addClass('eicon-loading eicon-animation-spin');
174
175 // Perform cache refresh
176 $.ajax({
177 type: 'POST',
178 url: ajaxurl,
179 data: {
180 action: 'jltma_refresh_templates_cache',
181 _wpnonce: MasterAddonsData.refresh_cache_nonce
182 },
183 success: function(response) {
184 if (response.success) {
185
186 // Update cache count if cache status element exists
187 var $cacheStatus = $('#ma-el-template-cache-status');
188 if ($cacheStatus.length && response.data.total_templates) {
189 var newCount = response.data.total_templates;
190 $cacheStatus.find('.cache-count').text(newCount);
191 $cacheStatus.attr('title', 'Cache Status: ' + newCount + ' templates cached');
192 if (newCount > 0) {
193 $cacheStatus.show();
194 } else {
195 $cacheStatus.hide();
196 }
197 }
198
199 // Reset button state
200 $button.removeClass('updating');
201 $icon.removeClass('eicon-loading eicon-animation-spin').addClass('eicon-sync');
202
203 // Force refresh of current tab content by reloading the modal
204 setTimeout(function() {
205 // Find the currently active tab to refresh
206 var $activeTab = $('#ma-el-template-modal-header-tabs input:checked');
207 if ($activeTab.length > 0) {
208 // Trigger click on active tab to reload content
209 $activeTab.trigger('change');
210 } else {
211 // Fallback: reload entire modal content if possible
212 var $modalContent = $('#ma-el-template-library-content');
213 if ($modalContent.length > 0) {
214 $modalContent.trigger('refresh');
215 }
216 }
217 }, 500);
218
219 // Show success feedback
220 $button.attr('title', 'Cache refreshed successfully!');
221 setTimeout(function() {
222 $button.attr('title', 'Refresh Cache');
223 }, 3000);
224
225 } else {
226 console.error('Failed to refresh template cache:', response.data ? response.data.message : 'Unknown error');
227 $button.removeClass('updating');
228 $icon.removeClass('eicon-loading eicon-animation-spin').addClass('eicon-sync');
229 }
230 },
231 error: function(xhr, status, error) {
232 console.error('AJAX error refreshing template cache:', error);
233 $button.removeClass('updating');
234 $icon.removeClass('eicon-loading eicon-animation-spin').addClass('eicon-sync');
235 }
236 });
237 });
238 }
239
240 // Initialize Macy.js masonry layout for template grid
241 function initMacyLayout() {
242 var $container = $('#ma-el-modal-templates-container');
243 if ($container.length === 0) {
244 $container = $('.ma-el-templates-list');
245 }
246 if ($container.length === 0) return;
247
248 // Destroy existing instance
249 if (macyInstance) {
250 macyInstance.remove();
251 macyInstance = null;
252 }
253
254 if (typeof Macy === 'undefined') return;
255
256 // Only init if container has children
257 if ($container.children().length === 0) return;
258
259 macyInstance = Macy({
260 container: $container[0],
261 waitForImages: true,
262 trueOrder: false,
263 margin: 20,
264 columns: 5,
265 breakAt: {
266 1370: 4,
267 940: 3,
268 520: 2,
269 400: 1
270 }
271 });
272
273 // Multiple recalculations like Royal Elementor Addons pattern
274 setTimeout(function() {
275 if (macyInstance) macyInstance.recalculate(true);
276 }, 300);
277 setTimeout(function() {
278 if (macyInstance) macyInstance.recalculate(true);
279 }, 600);
280 setTimeout(function() {
281 if (macyInstance) macyInstance.recalculate(true);
282 }, 1200);
283 }
284
285 // Tab preloader functionality
286 function initTabPreloader() {
287 // Add preloader for tab switching
288 $(document).on('click', '#ma-el-template-modal-header-tabs label', function(e) {
289 var $clickedLabel = $(this);
290 var $input = $clickedLabel.find('input');
291
292 // Don't show loader if this tab is already active
293 if ($input.is(':checked')) {
294 return;
295 }
296
297 // Show loading state
298 showTabLoader();
299
300 // Set a timeout to ensure loader is shown
301 setTimeout(function() {
302 $input.prop('checked', true).trigger('change');
303 }, 50);
304 });
305
306 // Monitor for tab content loading completion
307 $(document).on('DOMSubtreeModified', '#ma-el-template-library-content', function() {
308 hideTabLoader();
309 });
310
311 // Fallback: Hide loader after reasonable time
312 var loaderTimeout;
313 $(document).on('change', '#ma-el-template-modal-header-tabs input[type="radio"]', function() {
314 clearTimeout(loaderTimeout);
315 loaderTimeout = setTimeout(function() {
316 hideTabLoader();
317 }, 3000); // Hide after 3 seconds max
318 });
319
320 // Modern browsers: Use MutationObserver instead of deprecated DOMSubtreeModified
321 if (typeof MutationObserver !== 'undefined') {
322 var observer = new MutationObserver(function(mutations) {
323 mutations.forEach(function(mutation) {
324 if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
325 // Check if template content was added
326 var hasTemplateContent = false;
327 for (var i = 0; i < mutation.addedNodes.length; i++) {
328 var node = mutation.addedNodes[i];
329 if (node.nodeType === 1) { // Element node
330 if ($(node).find('#ma-el-modal-templates-container').length > 0 ||
331 $(node).is('#ma-el-modal-templates-container') ||
332 $(node).find('.elementor-template-library-template').length > 0) {
333 hasTemplateContent = true;
334 break;
335 }
336 }
337 }
338 if (hasTemplateContent) {
339 setTimeout(function() {
340 hideTabLoader();
341 // Reinitialize or refresh Macy when new content is loaded
342 if (macyInstance) {
343 macyInstance.recalculate(true);
344 } else {
345 initMacyLayout();
346 }
347 }, 200);
348 }
349 }
350 });
351 });
352
353 // Start observing when modal is present
354 $(document).on('DOMNodeInserted', '#ma-el-template-library-content', function() {
355 observer.observe(this, {
356 childList: true,
357 subtree: true
358 });
359 });
360 }
361 }
362
363 function showTabLoader() {
364 var $modalContent = $('#ma-el-template-library-content');
365 if ($modalContent.length > 0) {
366 // Add loading overlay
367 if ($modalContent.find('.ma-el-tab-loading-overlay').length === 0) {
368 var loaderHtml = '<div class="ma-el-tab-loading-overlay">' +
369 '<div class="elementor-loader-wrapper">' +
370 '<div class="elementor-loader">' +
371 '<div class="elementor-loader-box"></div>' +
372 '<div class="elementor-loader-box"></div>' +
373 '<div class="elementor-loader-box"></div>' +
374 '<div class="elementor-loader-box"></div>' +
375 '</div>' +
376 '<div class="elementor-loading-title">Loading Templates...</div>' +
377 '</div>' +
378 '</div>';
379 $modalContent.append(loaderHtml);
380 }
381 $modalContent.find('.ma-el-tab-loading-overlay').fadeIn(150);
382 }
383 }
384
385 function hideTabLoader() {
386 var $loadingOverlay = $('.ma-el-tab-loading-overlay');
387 if ($loadingOverlay.length > 0) {
388 $loadingOverlay.fadeOut(150, function() {
389 $(this).remove();
390 });
391 }
392 }
393
394 // Template search functionality
395 function initTemplateSearch() {
396 var searchTimeout = null;
397
398 // Handle search input
399 $(document).on('keyup input', '#ma-el-template-search-input', function(e) {
400 var $input = $(this);
401 var searchTerm = $input.val().toLowerCase().trim();
402
403 // Clear previous timeout
404 if (searchTimeout) {
405 clearTimeout(searchTimeout);
406 }
407
408 // Debounce search
409 searchTimeout = setTimeout(function() {
410 performTemplateSearch(searchTerm);
411 }, 300);
412 });
413
414 // Clear search on escape
415 $(document).on('keydown', '#ma-el-template-search-input', function(e) {
416 if (e.keyCode === 27) { // Escape key
417 $(this).val('');
418 performTemplateSearch('');
419 }
420 });
421
422 }
423
424 function performTemplateSearch(searchTerm) {
425 // Look for templates in both possible containers
426 var $templates = $('#ma-el-modal-templates-container .elementor-template-library-template, .ma-el-templates-list .elementor-template-library-template');
427 var $noResults = $('.ma-el-no-search-results');
428 var $container = $('#ma-el-modal-templates-container');
429 var $templatesContainer = $('.ma-el-templates-list');
430
431 // Use templates list container if it exists, otherwise fall back to modal container
432 if ($templatesContainer.length > 0) {
433 $container = $templatesContainer;
434 }
435
436 if (searchTerm === '') {
437 // Show all templates and respect any active keyword filter
438 var selectedKeyword = $('.ma-el-keyword-filter.active').data('keyword');
439 if (selectedKeyword) {
440 performKeywordFilter(selectedKeyword);
441 } else {
442 $templates.show();
443 $noResults.remove();
444 refreshMasonryLayout();
445 }
446 return;
447 }
448
449 var visibleCount = 0;
450 var selectedKeyword = $('.ma-el-keyword-filter.active').data('keyword');
451
452 $templates.each(function() {
453 var $template = $(this);
454 var templateName = ($template.find('.elementor-template-library-template-name').text() || '').toLowerCase();
455 var templateTitle = ($template.attr('data-title') || '').toLowerCase();
456 var templateKeywords = ($template.attr('data-keywords') || '').toLowerCase();
457
458 // Check search match
459 var searchMatch = templateName.indexOf(searchTerm) !== -1 ||
460 templateTitle.indexOf(searchTerm) !== -1 ||
461 templateKeywords.indexOf(searchTerm) !== -1;
462
463 // Check keyword filter match if one is selected
464 var keywordMatch = !selectedKeyword || templateKeywords.indexOf(selectedKeyword.toLowerCase()) !== -1;
465
466 // Show template if both search and keyword filter match
467 if (searchMatch && keywordMatch) {
468 $template.show();
469 visibleCount++;
470 } else {
471 $template.hide();
472 }
473 });
474
475 // Show no results message if needed
476 if (visibleCount === 0) {
477 if ($noResults.length === 0) {
478 var noResultsHtml = '<div class="ma-el-no-search-results">' +
479 '<div class="ma-el-no-results-content">' +
480 '<i class="eicon-search"></i>' +
481 '<h3>No templates found</h3>' +
482 '<p>Try searching with different keywords or check other tabs.</p>' +
483 '</div>' +
484 '</div>';
485 $container.append(noResultsHtml);
486 }
487 } else {
488 $noResults.remove();
489 }
490
491 // Force masonry layout refresh
492 refreshMasonryLayout();
493 }
494
495 function performKeywordFilter(keyword) {
496 var $templates = $('#ma-el-modal-templates-container .elementor-template-library-template, .ma-el-templates-list .elementor-template-library-template');
497 var $noResults = $('.ma-el-no-filter-results');
498 var $container = $('.ma-el-templates-list').length > 0 ? $('.ma-el-templates-list') : $('#ma-el-modal-templates-container');
499 var searchTerm = $('#ma-el-template-search-input').val().toLowerCase().trim();
500
501 if (!keyword) {
502 // Show all templates but respect search term
503 if (searchTerm) {
504 performTemplateSearch(searchTerm);
505 } else {
506 $templates.show();
507 $noResults.remove();
508 refreshMasonryLayout();
509 }
510 return;
511 }
512
513 var visibleCount = 0;
514
515 $templates.each(function() {
516 var $template = $(this);
517 var templateKeywords = ($template.attr('data-keywords') || '').toLowerCase();
518 var templateName = ($template.find('.elementor-template-library-template-name').text() || '').toLowerCase();
519 var templateTitle = ($template.attr('data-title') || '').toLowerCase();
520
521 // Check keyword filter match
522 var keywordMatch = templateKeywords.indexOf(keyword.toLowerCase()) !== -1;
523
524 // Check search match if there's a search term
525 var searchMatch = !searchTerm ||
526 templateName.indexOf(searchTerm) !== -1 ||
527 templateTitle.indexOf(searchTerm) !== -1 ||
528 templateKeywords.indexOf(searchTerm) !== -1;
529
530 // Show template if both keyword and search match
531 if (keywordMatch && searchMatch) {
532 $template.show();
533 visibleCount++;
534 } else {
535 $template.hide();
536 }
537 });
538
539 // Show no results message if needed
540 if (visibleCount === 0) {
541 if ($noResults.length === 0) {
542 var noResultsHtml = '<div class="ma-el-no-filter-results">' +
543 '<div class="ma-el-no-results-content">' +
544 '<i class="eicon-filter"></i>' +
545 '<h3>No templates found</h3>' +
546 '<p>Try selecting a different filter or clear the search.</p>' +
547 '</div>' +
548 '</div>';
549 $container.append(noResultsHtml);
550 }
551 } else {
552 $noResults.remove();
553 }
554
555 // Force masonry layout refresh
556 refreshMasonryLayout();
557 }
558
559 // Initialize Macy.js layout
560 (function() {
561 if (window.__macyLayoutInit) return;
562 window.__macyLayoutInit = true;
563
564
565 const log = (...a) => console.log("[MacyFix]", ...a);
566 const wait = (ms) => new Promise((r) => setTimeout(r, ms));
567
568 const findContainer = () =>
569 document.querySelector("#ma-el-modal-templates-container") ||
570 document.querySelector(".ma-el-templates-list") ||
571 document.querySelector(".ma-el-templates-grid");
572
573 const getCols = (w) => (w < 480 ? 1 : w < 520 ? 2 : w < 600 ? 3 : w < 768 ? 4 : 5);
574
575 async function waitImages(container) {
576 if (!container) return;
577 const imgs = Array.from(container.querySelectorAll("img"));
578 if (!imgs.length) return;
579 await Promise.allSettled(
580 imgs.map(
581 (img) =>
582 new Promise((res) => {
583 if (img.complete && img.naturalWidth) return res();
584 img.addEventListener("load", res, {
585 once: true
586 });
587 img.addEventListener("error", res, {
588 once: true
589 });
590 })
591 )
592 );
593 }
594
595 async function refreshMacy(delay = 150) {
596 await wait(delay);
597 // Delegate to the single initMacyLayout() function
598 // which uses hardcoded 5 columns with proper breakpoints
599 initMacyLayout();
600 }
601
602 window.__refreshMacyLayout = refreshMacy;
603
604
605 const tabSelector =
606 "#views-ma-el-template-modal-tabs-items, " +
607 "#ma-el-template-modal-header-tabs, " +
608 ".elementor-template-library-menu-item, " +
609 ".MuiTabs-root [role='tab']";
610
611 jQuery(document)
612 .off("click.MacyFix change.MacyFix")
613 .on("click.MacyFix change.MacyFix", tabSelector, () => refreshMacy(250));
614
615 jQuery(document).off("ajaxComplete.MacyFix").on("ajaxComplete.MacyFix", () => refreshMacy(300));
616
617 let resizeTimer;
618 window.addEventListener("resize", () => {
619 clearTimeout(resizeTimer);
620 resizeTimer = setTimeout(() => refreshMacy(100), 180);
621 });
622
623 if (window.MutationObserver) {
624 const tabRoot =
625 document.querySelector(".MuiTabs-root") ||
626 document.querySelector("#views-ma-el-template-modal-tabs-items");
627 if (tabRoot) {
628 const obs = new MutationObserver(() => refreshMacy(200));
629 obs.observe(tabRoot, {
630 attributes: true,
631 subtree: true
632 });
633 log("Tab observer active");
634 }
635 }
636
637 (async () => {
638 let tries = 0;
639 const iv = setInterval(async () => {
640 const container = findContainer();
641 if (container || tries > 20) {
642 clearInterval(iv);
643 if (container) await refreshMacy(100);
644 // Silently stop - container only exists when template library is open
645 }
646 tries++;
647 }, 200);
648 })();
649 })();
650
651 function refreshMasonryLayout() {
652 // Use Macy.js for layout refresh
653 if (macyInstance) {
654 // Recalculate the layout
655 macyInstance.recalculate(true);
656
657 // Also rerun the layout after images potentially load
658 setTimeout(function() {
659 macyInstance.recalculate(true);
660 }, 500);
661 } else {
662 // Fallback: Try to initialize Macy if not already done
663 initMacyLayout();
664 }
665
666 // Trigger window resize for compatibility
667 setTimeout(function() {
668 $(window).trigger('resize');
669 }, 50);
670 }
671
672 function initFiltersAndKeywords() {
673 // Wait for both DOM structure and Marionette views to be available
674 function setupFiltersAndKeywords() {
675 var $filtersList = $('.ma-el-filters-list');
676 var $keywordsList = $('.ma-el-keywords-list');
677 var $templatesWrap = $('.ma-el-modal-templates-wrap');
678
679 if ($filtersList.length === 0 || $keywordsList.length === 0 || $templatesWrap.length === 0) {
680 return false; // Not ready yet
681 }
682
683 // Check if Marionette has already populated these regions
684 if ($filtersList.children().length > 0 || $keywordsList.children().length > 0) {
685 return true; // Already populated by Marionette
686 }
687
688 // Only populate if Marionette hasn't done so already
689 // Populate filters list (categories/tags)
690 if ($filtersList.children().length === 0) {
691 var filtersHtml = '<h4>Categories</h4>' +
692 '<div class="ma-el-filter-items">' +
693 '<div class="ma-el-filter-item ma-el-category-filter active" data-category="all">' +
694 '<span>All Categories</span>' +
695 '</div>' +
696 '<div class="ma-el-filter-item ma-el-category-filter" data-category="blocks">' +
697 '<span>Blocks</span>' +
698 '</div>' +
699 '<div class="ma-el-filter-item ma-el-category-filter" data-category="pages">' +
700 '<span>Pages</span>' +
701 '</div>' +
702 '<div class="ma-el-filter-item ma-el-category-filter" data-category="sections">' +
703 '<span>Sections</span>' +
704 '</div>' +
705 '</div>';
706 $filtersList.html(filtersHtml);
707 }
708
709 // Populate keywords list (widget types)
710 // if ($keywordsList.children().length === 0) {
711 // var keywordsHtml = '<h4>Widgets</h4>' +
712 // '<div class="ma-el-keyword-items">' +
713 // '<div class="ma-el-filter-item ma-el-keyword-filter active" data-keyword="">' +
714 // '<span>All Widgets</span>' +
715 // '</div>' +
716 // '<div class="ma-el-filter-item ma-el-keyword-filter" data-keyword="call-to-action">' +
717 // '<span>Call To Action</span>' +
718 // '</div>' +
719 // '<div class="ma-el-filter-item ma-el-keyword-filter" data-keyword="counter">' +
720 // '<span>Counter</span>' +
721 // '</div>' +
722 // '<div class="ma-el-filter-item ma-el-keyword-filter" data-keyword="creative-button">' +
723 // '<span>Creative Button</span>' +
724 // '</div>' +
725 // '<div class="ma-el-filter-item ma-el-keyword-filter" data-keyword="faq">' +
726 // '<span>FAQ</span>' +
727 // '</div>' +
728 // '<div class="ma-el-filter-item ma-el-keyword-filter" data-keyword="flipbox">' +
729 // '<span>Flipbox</span>' +
730 // '</div>' +
731 // '<div class="ma-el-filter-item ma-el-keyword-filter" data-keyword="heading">' +
732 // '<span>Heading</span>' +
733 // '</div>' +
734 // '<div class="ma-el-filter-item ma-el-keyword-filter" data-keyword="hero-image">' +
735 // '<span>Hero Image</span>' +
736 // '</div>' +
737 // '<div class="ma-el-filter-item ma-el-keyword-filter" data-keyword="hero-slideshow">' +
738 // '<span>Hero Slideshow</span>' +
739 // '</div>' +
740 // '<div class="ma-el-filter-item ma-el-keyword-filter" data-keyword="infobox">' +
741 // '<span>Infobox</span>' +
742 // '</div>' +
743 // '<div class="ma-el-filter-item ma-el-keyword-filter" data-keyword="progress-bar">' +
744 // '<span>Progress Bar</span>' +
745 // '</div>' +
746 // '<div class="ma-el-filter-item ma-el-keyword-filter" data-keyword="team-members">' +
747 // '<span>Team Members</span>' +
748 // '</div>' +
749 // '<div class="ma-el-filter-item ma-el-keyword-filter" data-keyword="testimonial">' +
750 // '<span>Testimonial</span>' +
751 // '</div>' +
752 // '<div class="ma-el-filter-item ma-el-keyword-filter" data-keyword="text-editor">' +
753 // '<span>Text Editor</span>' +
754 // '</div>' +
755 // '</div>';
756 // $keywordsList.html(keywordsHtml);
757 // }
758
759 // Add event handlers for filters
760 $(document).off('click', '.ma-el-category-filter').on('click', '.ma-el-category-filter', function(e) {
761 e.preventDefault();
762 var $this = $(this);
763 var category = $this.data('category');
764
765 // Update active state
766 $('.ma-el-category-filter').removeClass('active');
767 $this.addClass('active');
768
769 // Apply category filter (placeholder for now)
770 performCategoryFilter(category);
771 });
772
773 // $(document).off('click', '.ma-el-keyword-filter').on('click', '.ma-el-keyword-filter', function(e) {
774 // e.preventDefault();
775 // var $this = $(this);
776 // var keyword = $this.data('keyword');
777
778 // // Update active state
779 // $('.ma-el-keyword-filter').removeClass('active');
780 // $this.addClass('active');
781
782 // // Apply keyword filter
783 // performKeywordFilter(keyword);
784 // });
785
786 return true; // Successfully setup
787 }
788
789 // Wait longer before trying to setup to allow Marionette to initialize first
790 setTimeout(function() {
791 if (!setupFiltersAndKeywords()) {
792 // If not ready, monitor for DOM changes
793 var attempts = 0;
794 var maxAttempts = 30;
795
796 var setupInterval = setInterval(function() {
797 attempts++;
798
799 if (setupFiltersAndKeywords() || attempts >= maxAttempts) {
800 clearInterval(setupInterval);
801 }
802 }, 200);
803 }
804 }, 1000); // Wait 1 second for Marionette to initialize
805 }
806
807 function performCategoryFilter(category) {
808 // Placeholder for category filtering
809 // This would filter templates by category (blocks, pages, sections, etc.)
810 console.log('Category filter:', category);
811
812 // For now, just trigger a layout refresh
813 refreshMasonryLayout();
814 }
815
816 // Initialize when document is ready
817 initCacheRefresh();
818
819 // Initialize Template Importer lazy loading
820 initTemplateImporterLazyLoad();
821
822 function initTemplateImporterLazyLoad() {
823 var scrollBound = false;
824
825 function setupScrollPagination() {
826 if (scrollBound) return;
827
828 // Find the correct modal: Master Addons modal has id "ma-el-modal-template"
829 var $modalElement = $('#ma-el-modal-template');
830 if ($modalElement.length === 0) {
831 // Fallback: find the dialog message widget content (the scrollable part)
832 $modalElement = $('.elementor-templates-modal .dialog-message');
833 }
834 if ($modalElement.length === 0) return;
835
836 // Add loading indicator after the templates container
837 var $container = $modalElement.find('.ma-el-templates-list, #ma-el-modal-templates-container').first();
838 if ($container.length && !$container.find('.ma-el-template-loading-more').length) {
839 $container.append('<div class="ma-el-template-loading-more" style="display:none; text-align:center; padding:30px 0;"><img src="' + (MasterAddonsData.master_image_dir || '') + '" style="width:28px;height:28px;animation:spin 1s linear infinite;" /><p style="margin:8px 0 0;color:#6814cd;font-weight:500;">Loading more templates...</p></div>');
840 }
841
842 // Find scrollable element inside the modal
843 var scrollEl = null;
844 $modalElement.find('.dialog-message, .dialog-content, .dialog-widget-content').each(function() {
845 if (this.scrollHeight > this.clientHeight) {
846 scrollEl = this;
847 return false;
848 }
849 });
850 if (!scrollEl) scrollEl = $modalElement[0];
851
852 $(scrollEl).off('scroll.template-lazy').on('scroll.template-lazy', function() {
853 var el = this;
854 var scrollTop = el.scrollTop;
855 var scrollHeight = el.scrollHeight;
856 var clientHeight = el.clientHeight;
857
858 // Load more when user scrolled 50px+ and is near the bottom
859 if (scrollTop > 50 && scrollTop + clientHeight >= scrollHeight - 300) {
860 // Use the editor.js loadMoreTemplates method via the global reference
861 if (window.MasterAddonsEditor && typeof window.MasterAddonsEditor.loadMoreTemplates === 'function') {
862 window.MasterAddonsEditor.loadMoreTemplates();
863 }
864 }
865 });
866
867 scrollBound = true;
868 }
869
870 // Reset scroll binding when tab changes (new content renders)
871 $(document).on('change', '#ma-el-template-modal-header-tabs input[type="radio"]', function() {
872 scrollBound = false;
873 });
874
875 // Use MutationObserver to detect when modal content is rendered
876 var observer = new MutationObserver(function(mutations) {
877 for (var i = 0; i < mutations.length; i++) {
878 if (mutations[i].addedNodes.length > 0) {
879 var $modal = $('#ma-el-modal-template');
880 if ($modal.length > 0 && $modal.find('.elementor-template-library-template').length > 0) {
881 setTimeout(setupScrollPagination, 300);
882 break;
883 }
884 }
885 }
886 });
887
888 observer.observe(document.body, { childList: true, subtree: true });
889 }
890 });
891 </script>
892 <?php
893 }
894
895
896 public static function get_instance()
897 {
898
899 if (self::$instance == null) {
900 self::$instance = new self;
901 }
902 return self::$instance;
903 }
904 }
905 }
906