PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.44
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.44
51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 All 40 releases
king-addons / includes / extensions / Site_Preloader / assets / admin.js

admin.js in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.44, at includes/extensions/Site_Preloader/assets/admin.js

1,045 lines 41.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Site Preloader Admin Script.
3 *
4 * Handles admin UI interactions, live preview, color pickers, media uploaders, and AJAX operations.
5 * Apple-inspired smooth interactions.
6 *
7 * @package King_Addons
8 * @since 1.0.0
9 */
10
11 (function ($) {
12 'use strict';
13
14 /**
15 * Admin Controller
16 */
17 const KngPreloaderAdmin = {
18
19 /**
20 * Cached elements
21 */
22 elements: {
23 form: null,
24 preview: null,
25 previewFrame: null,
26 previewContent: null,
27 colorPickers: null,
28 mediaButtons: null,
29 tabLinks: null,
30 rulesList: null
31 },
32
33 /**
34 * Current settings state
35 */
36 settings: {},
37
38 /**
39 * Initialize admin
40 */
41 init: function () {
42 this.cacheElements();
43 this.initColorPickers();
44 this.initMediaUploaders();
45 this.initTabs();
46 this.initPresetSelection();
47 this.initLivePreview();
48 this.initRulesManager();
49 this.initImportExport();
50 this.initDarkModeToggle();
51 this.initFormSubmit();
52 this.initPreviewModal();
53 },
54
55 /**
56 * Cache DOM elements
57 */
58 cacheElements: function () {
59 this.elements.form = $('#kng-preloader-settings-form');
60 this.elements.preview = $('.ka-preloader-live-preview');
61 this.elements.previewFrame = $('.ka-preloader-preview-frame');
62 this.elements.previewContent = $('.ka-preloader-preview-content');
63 this.elements.colorPickers = $('.ka-preloader-color-input');
64 this.elements.mediaButtons = $('.ka-preloader-media-button');
65 this.elements.tabLinks = $('.ka-preloader-tab-link');
66 this.elements.rulesList = $('.ka-preloader-rules-list');
67 },
68
69 /**
70 * Initialize color pickers
71 */
72 initColorPickers: function () {
73 this.elements.colorPickers.each(function () {
74 const $input = $(this);
75 const $preview = $input.siblings('.ka-preloader-color-preview');
76
77 // Update preview on input change
78 $input.on('input change', function () {
79 const color = $(this).val();
80 $preview.css('background-color', color);
81 KngPreloaderAdmin.updatePreview();
82 });
83
84 // Initialize preview color
85 $preview.css('background-color', $input.val());
86 });
87
88 // Initialize WordPress color picker if available
89 if ($.fn.wpColorPicker) {
90 $('.ka-preloader-wp-color-picker').wpColorPicker({
91 change: function (event, ui) {
92 KngPreloaderAdmin.updatePreview();
93 },
94 clear: function () {
95 KngPreloaderAdmin.updatePreview();
96 }
97 });
98 }
99 },
100
101 /**
102 * Initialize media uploaders
103 */
104 initMediaUploaders: function () {
105 this.elements.mediaButtons.on('click', function (e) {
106 e.preventDefault();
107
108 const $button = $(this);
109 const $container = $button.closest('.ka-preloader-media-field');
110 const $input = $container.find('input[type="hidden"]');
111 const $preview = $container.find('.ka-preloader-media-preview');
112 const $removeBtn = $container.find('.ka-preloader-media-remove');
113
114 // Create media frame
115 const frame = wp.media({
116 title: $button.data('title') || 'Select Image',
117 button: {
118 text: $button.data('button-text') || 'Use Image'
119 },
120 multiple: false
121 });
122
123 // Handle selection
124 frame.on('select', function () {
125 const attachment = frame.state().get('selection').first().toJSON();
126
127 $input.val(attachment.id);
128
129 let imgUrl = attachment.url;
130 if (attachment.sizes && attachment.sizes.thumbnail) {
131 imgUrl = attachment.sizes.thumbnail.url;
132 }
133
134 $preview.html('<img src="' + imgUrl + '" alt="" />').show();
135 $removeBtn.show();
136 $button.text('Change Image');
137
138 KngPreloaderAdmin.updatePreview();
139 });
140
141 frame.open();
142 });
143
144 // Handle remove button
145 $('.ka-preloader-media-remove').on('click', function (e) {
146 e.preventDefault();
147
148 const $button = $(this);
149 const $container = $button.closest('.ka-preloader-media-field');
150 const $input = $container.find('input[type="hidden"]');
151 const $preview = $container.find('.ka-preloader-media-preview');
152 const $uploadBtn = $container.find('.ka-preloader-media-button');
153
154 $input.val('');
155 $preview.empty().hide();
156 $button.hide();
157 $uploadBtn.text('Upload Image');
158
159 KngPreloaderAdmin.updatePreview();
160 });
161 },
162
163 /**
164 * Initialize tabs
165 */
166 initTabs: function () {
167 this.elements.tabLinks.on('click', function (e) {
168 e.preventDefault();
169
170 const $link = $(this);
171 const tab = $link.data('tab');
172
173 // Update URL without reload
174 const url = new URL(window.location);
175 url.searchParams.set('tab', tab);
176 window.history.replaceState({}, '', url);
177
178 // Update active states
179 KngPreloaderAdmin.elements.tabLinks.removeClass('active');
180 $link.addClass('active');
181
182 // Show target panel
183 $('.ka-preloader-tab-panel').removeClass('active');
184 $('#ka-preloader-tab-' + tab).addClass('active');
185 });
186 },
187
188 /**
189 * Initialize preset selection
190 */
191 initPresetSelection: function () {
192 $(document).on('click', '.ka-preloader-preset-card', function () {
193 const $card = $(this);
194 const presetId = $card.data('preset');
195
196 // Update active state
197 $('.ka-preloader-preset-card').removeClass('active');
198 $card.addClass('active');
199
200 // Update hidden input
201 $('input[name="preset"]').val(presetId);
202
203 // Update preview
204 KngPreloaderAdmin.updatePreview();
205 });
206 },
207
208 /**
209 * Initialize live preview
210 */
211 initLivePreview: function () {
212 // Collect all form inputs
213 const $formInputs = this.elements.form.find('input, select, textarea');
214
215 // Update preview on input change
216 $formInputs.on('input change', function () {
217 KngPreloaderAdmin.debounce(KngPreloaderAdmin.updatePreview, 150)();
218 });
219
220 // Initial preview render
221 this.updatePreview();
222 },
223
224 /**
225 * Update live preview
226 */
227 updatePreview: function () {
228 if (!this.elements.previewContent.length) {
229 return;
230 }
231
232 // Collect current settings
233 const settings = this.collectFormSettings();
234
235 // Update preview CSS variables
236 const $frame = this.elements.previewFrame;
237 $frame.css({
238 '--kng-preloader-accent': settings.accent_color || '#0071e3',
239 '--kng-preloader-bg': settings.background_color || '#ffffff',
240 '--kng-preloader-text': settings.text_color || '#1d1d1f',
241 '--kng-preloader-size': (settings.animation_size || 48) + 'px'
242 });
243
244 // Update background overlay
245 const $overlay = this.elements.previewContent.find('.ka-preloader-preview-overlay');
246 if ($overlay.length) {
247 $overlay.css('background-color', settings.background_color || '#ffffff');
248 }
249
250 // Update preset animation
251 this.updatePresetAnimation(settings.preset || 'spinner-circle');
252
253 // Update text
254 const $text = this.elements.previewContent.find('.ka-preloader-preview-text');
255 if (settings.text_content) {
256 $text.text(settings.text_content).show();
257 } else {
258 $text.hide();
259 }
260 },
261
262 /**
263 * Update preset animation in preview
264 */
265 updatePresetAnimation: function (presetId) {
266 const $animation = this.elements.previewContent.find('.ka-preloader-preview-animation');
267 if (!$animation.length) {
268 return;
269 }
270
271 // Generate preset HTML
272 const presetHtml = this.getPresetHtml(presetId);
273 $animation.html(presetHtml);
274 },
275
276 /**
277 * Get preset HTML
278 */
279 getPresetHtml: function (presetId) {
280 const presets = {
281 'spinner-circle': '<div class="kng-preloader-spinner-circle"></div>',
282 'dual-ring': '<div class="kng-preloader-dual-ring"></div>',
283 'dots-bounce': '<div class="kng-preloader-dots-bounce"><span></span><span></span><span></span></div>',
284 'bar-loader': '<div class="kng-preloader-bar-loader"><div class="kng-preloader-bar-loader__bar"></div></div>',
285 'pulse-logo': '<div class="kng-preloader-pulse-logo"><div class="kng-preloader-pulse-logo__circle"></div></div>',
286 'minimal-line': '<div class="kng-preloader-minimal-line"><div class="kng-preloader-minimal-line__track"><div class="kng-preloader-minimal-line__bar"></div></div></div>',
287 'gradient-spinner': '<div class="kng-preloader-gradient-spinner"></div>',
288 'fade-text': '<div class="kng-preloader-fade-text"><span style="animation-delay: 0s">L</span><span style="animation-delay: 0.1s">o</span><span style="animation-delay: 0.2s">a</span><span style="animation-delay: 0.3s">d</span><span style="animation-delay: 0.4s">i</span><span style="animation-delay: 0.5s">n</span><span style="animation-delay: 0.6s">g</span></div>',
289 'cube-grid': '<div class="kng-preloader-cube-grid">' + '<div class="kng-preloader-cube-grid__cube"></div>'.repeat(9) + '</div>',
290 'wave-bars': '<div class="kng-preloader-wave-bars">' + '<div class="kng-preloader-wave-bars__bar"></div>'.repeat(5) + '</div>',
291 'rotating-squares': '<div class="kng-preloader-rotating-squares"><div class="kng-preloader-rotating-squares__square"></div><div class="kng-preloader-rotating-squares__square"></div></div>',
292 'morphing-circle': '<div class="kng-preloader-morphing-circle"></div>'
293 };
294
295 return presets[presetId] || presets['spinner-circle'];
296 },
297
298 /**
299 * Collect form settings
300 */
301 collectFormSettings: function () {
302 const settings = {};
303
304 this.elements.form.find('input, select, textarea').each(function () {
305 const $field = $(this);
306 const name = $field.attr('name');
307
308 if (!name) return;
309
310 if ($field.is(':checkbox')) {
311 settings[name] = $field.is(':checked') ? '1' : '0';
312 } else if ($field.is(':radio')) {
313 if ($field.is(':checked')) {
314 settings[name] = $field.val();
315 }
316 } else {
317 settings[name] = $field.val();
318 }
319 });
320
321 return settings;
322 },
323
324 /**
325 * Initialize rules manager
326 */
327 initRulesManager: function () {
328 // Add rule button - support both class and ID
329 $(document).on('click', '#ka-add-rule-btn, .ka-preloader-add-rule', function (e) {
330 e.preventDefault();
331 KngPreloaderAdmin.addNewRule();
332 });
333
334 // Remove rule button (both classes) - use deleteRule with AJAX
335 $(document).on('click', '.ka-preloader-rule-remove, .ka-rule-delete-btn', function (e) {
336 e.preventDefault();
337 e.stopPropagation();
338
339 if (!confirm(kngPreloaderAdmin.strings.confirmDelete || 'Are you sure you want to delete this rule?')) {
340 return;
341 }
342
343 const $rule = $(this).closest('.ka-preloader-rule-item');
344 KngPreloaderAdmin.deleteRule($rule);
345 });
346
347 // Edit rule button - toggle details
348 $(document).on('click', '.ka-rule-edit-btn', function (e) {
349 e.preventDefault();
350 e.stopPropagation();
351 const $rule = $(this).closest('.ka-preloader-rule-item');
352 const $details = $rule.find('.ka-preloader-rule-item__details');
353 $details.slideToggle(200);
354 });
355
356 // Save rule button
357 $(document).on('click', '.ka-rule-save-btn', function (e) {
358 e.preventDefault();
359 const $rule = $(this).closest('.ka-preloader-rule-item');
360 KngPreloaderAdmin.saveRule($rule);
361 });
362
363 // Cancel rule button
364 $(document).on('click', '.ka-rule-cancel-btn', function (e) {
365 e.preventDefault();
366 const $rule = $(this).closest('.ka-preloader-rule-item');
367 KngPreloaderAdmin.cancelRuleEdit($rule);
368 });
369
370 // Action buttons (Show/Hide/Override) - both old and new class names
371 $(document).on('click', '.ka-rule-action-btn, .ka-rule-action-btns .ka-btn', function (e) {
372 e.preventDefault();
373 const $btn = $(this);
374 const $rule = $btn.closest('.ka-preloader-rule-item');
375 const action = $btn.data('action');
376
377 // Update active state
378 $btn.siblings().removeClass('active');
379 $btn.addClass('active');
380
381 // Store in hidden input if exists
382 $rule.find('input.ka-rule-action').val(action);
383
384 // Show/hide template row (only for override)
385 const $templateRow = $rule.find('.ka-rule-template-row');
386 const $colorsToggleRow = $rule.find('.ka-rule-colors-toggle-row');
387 const $colorsRow = $rule.find('.ka-rule-colors-row');
388
389 if (action === 'override') {
390 $templateRow.slideDown(200);
391 $colorsToggleRow.slideDown(200);
392 if ($rule.find('.ka-rule-override-colors').is(':checked')) {
393 $colorsRow.slideDown(200);
394 }
395 } else {
396 $templateRow.slideUp(200);
397 $colorsToggleRow.slideUp(200);
398 $colorsRow.slideUp(200);
399 }
400
401 // Update header badge
402 const actionLabels = { 'show': 'Show', 'hide': 'Hide', 'override': 'Override' };
403 const actionIcons = { 'show': 'visibility', 'hide': 'hidden', 'override': 'admin-customizer' };
404 $rule.find('.ka-preloader-rule-item__summary .ka-rule-action')
405 .removeClass('ka-rule-action--show ka-rule-action--hide ka-rule-action--override')
406 .addClass('ka-rule-action--' + action)
407 .html('<span class="dashicons dashicons-' + actionIcons[action] + '"></span> ' + actionLabels[action]);
408 });
409
410 // Toggle rule expansion (header click)
411 $(document).on('click', '.ka-preloader-rule-item__header', function (e) {
412 // Don't toggle if clicking buttons
413 if ($(e.target).closest('button, .ka-toggle, input').length) {
414 return;
415 }
416
417 const $rule = $(this).closest('.ka-preloader-rule-item');
418 const $details = $rule.find('.ka-preloader-rule-item__details');
419 $details.slideToggle(200);
420 });
421
422 // Toggle rule expansion (old class for JS-generated rules)
423 $(document).on('click', '.ka-preloader-rule-header', function (e) {
424 if ($(e.target).closest('button').length) {
425 return;
426 }
427
428 const $rule = $(this).closest('.ka-preloader-rule-item');
429 $rule.toggleClass('expanded');
430 });
431
432 // Rule condition change - show/hide appropriate fields
433 $(document).on('change', '.ka-rule-condition', function () {
434 const $rule = $(this).closest('.ka-preloader-rule-item');
435 const condition = $(this).val();
436
437 KngPreloaderAdmin.updateConditionFields($rule, condition);
438 });
439
440 // Also support old class names
441 $(document).on('change', '.ka-preloader-rule-condition, .ka-rule-condition-type', function () {
442 const $rule = $(this).closest('.ka-preloader-rule-item');
443 const condition = $(this).val();
444
445 KngPreloaderAdmin.updateRuleConditions($rule, condition);
446 });
447
448 // Override colors toggle
449 $(document).on('change', '.ka-rule-override-colors', function () {
450 const $rule = $(this).closest('.ka-preloader-rule-item');
451 const $colorRows = $rule.find('.ka-rule-colors-row, .ka-rule-colors-grid');
452
453 if ($(this).is(':checked')) {
454 $colorRows.slideDown(200);
455 } else {
456 $colorRows.slideUp(200);
457 }
458 });
459
460 // Rule type change
461 $(document).on('change', '.ka-preloader-rule-type', function () {
462 const $rule = $(this).closest('.ka-preloader-rule-item');
463 const type = $(this).val();
464
465 KngPreloaderAdmin.updateRuleConditions($rule, type);
466 });
467
468 // Initialize condition fields on page load
469 $('.ka-preloader-rule-item').each(function() {
470 const $rule = $(this);
471 const condition = $rule.find('.ka-rule-condition').val();
472 if (condition) {
473 KngPreloaderAdmin.updateConditionFields($rule, condition);
474 }
475 });
476 },
477
478 /**
479 * Update condition fields visibility based on selected condition
480 */
481 updateConditionFields: function ($rule, condition) {
482 const $pagesRow = $rule.find('.ka-rule-pages-row');
483 const $posttypeRow = $rule.find('.ka-rule-posttype-row');
484 const $urlRow = $rule.find('.ka-rule-url-row');
485
486 // Hide all conditional rows first
487 $pagesRow.hide();
488 $posttypeRow.hide();
489 $urlRow.hide();
490
491 // Show appropriate row based on condition
492 switch (condition) {
493 case 'specific_pages':
494 $pagesRow.slideDown(200);
495 break;
496 case 'post_type':
497 $posttypeRow.slideDown(200);
498 break;
499 case 'url_contains':
500 case 'url_equals':
501 $urlRow.slideDown(200);
502 break;
503 // Other conditions don't need additional fields
504 }
505 },
506
507 /**
508 * Save rule via AJAX
509 */
510 saveRule: function ($rule) {
511 const $saveBtn = $rule.find('.ka-rule-save-btn');
512 const originalText = $saveBtn.text();
513
514 // Get action from the active button or hidden input
515 let action = 'show';
516 const $activeAction = $rule.find('.ka-rule-action-btn.active, .ka-rule-action-btns .ka-btn.active');
517 if ($activeAction.length) {
518 action = $activeAction.data('action');
519 } else {
520 const $actionInput = $rule.find('input.ka-rule-action');
521 if ($actionInput.length && $actionInput.val()) {
522 action = $actionInput.val();
523 }
524 }
525
526 // Get condition
527 const condition = $rule.find('.ka-rule-condition').val() || 'specific_pages';
528
529 // Get pages as array
530 const pages = [];
531 $rule.find('.ka-rule-pages option:selected').each(function() {
532 pages.push($(this).val());
533 });
534
535 // Collect rule data with new structure
536 const ruleData = {
537 action: 'king_addons_preloader_save_rule',
538 nonce: kngPreloaderAdmin.nonce,
539 rule_id: $rule.attr('data-rule-id') || '',
540 enabled: $rule.find('.ka-rule-enabled').is(':checked') ? '1' : '0',
541 priority: $rule.find('.ka-rule-priority').val() || 10,
542 rule_action: action, // show / hide / override
543 condition: condition,
544 condition_value: $rule.find('.ka-rule-condition-value, .ka-rule-url-value, .ka-rule-posttype').val() || '',
545 pages: pages,
546 template: $rule.find('.ka-rule-template').val() || '',
547 override_colors: $rule.find('.ka-rule-override-colors').is(':checked') ? '1' : '0',
548 bg_color: $rule.find('.ka-rule-bg-color').val() || '#ffffff',
549 accent_color: $rule.find('.ka-rule-accent-color').val() || '#0071e3'
550 };
551
552 // Show saving state
553 $saveBtn.text('Saving...').prop('disabled', true);
554
555 console.log('Saving rule:', ruleData);
556
557 // Send AJAX request
558 $.ajax({
559 url: kngPreloaderAdmin.ajaxUrl,
560 type: 'POST',
561 data: ruleData,
562 success: function (response) {
563 console.log('Save response:', response);
564 if (response.success) {
565 // Update rule ID if it was a new rule
566 if (response.data.rule && response.data.rule.id) {
567 $rule.attr('data-rule-id', response.data.rule.id);
568 }
569
570 // Update header display
571 const actionLabels = { 'show': 'Show', 'hide': 'Hide', 'override': 'Override' };
572 const conditionLabels = {
573 'specific_pages': 'Specific Pages',
574 'all_pages': 'All Pages',
575 'front_page': 'Front Page',
576 'blog_page': 'Blog Page',
577 'all_posts': 'All Posts',
578 'post_type': 'Post Type',
579 'archive': 'Archive',
580 'search': 'Search',
581 '404': '404 Page',
582 'url_contains': 'URL Contains',
583 'url_equals': 'URL Equals'
584 };
585
586 $rule.find('.ka-preloader-rule-item__action').text(actionLabels[action] || action);
587 $rule.find('.ka-preloader-rule-item__type').text(conditionLabels[condition] || condition);
588
589 // Show pages count for specific_pages
590 if (condition === 'specific_pages' && pages.length > 0) {
591 $rule.find('.ka-preloader-rule-item__value').text(pages.length + ' page(s)');
592 } else {
593 $rule.find('.ka-preloader-rule-item__value').text(ruleData.condition_value || '');
594 }
595
596 // Hide details
597 $rule.find('.ka-preloader-rule-item__details').slideUp(200);
598
599 // Show success
600 $saveBtn.text('Saved!');
601 setTimeout(() => {
602 $saveBtn.text(originalText).prop('disabled', false);
603 }, 1500);
604 } else {
605 alert(response.data?.message || kngPreloaderAdmin.strings.error);
606 $saveBtn.text(originalText).prop('disabled', false);
607 }
608 },
609 error: function () {
610 alert(kngPreloaderAdmin.strings.error);
611 $saveBtn.text(originalText).prop('disabled', false);
612 }
613 });
614 },
615
616 /**
617 * Delete rule via AJAX
618 */
619 deleteRule: function ($rule) {
620 const ruleId = $rule.attr('data-rule-id');
621
622 // If it's a new unsaved rule, just remove from DOM
623 if (!ruleId || ruleId.startsWith('new_')) {
624 $rule.slideUp(200, function () {
625 $(this).remove();
626 KngPreloaderAdmin.checkEmptyRules();
627 });
628 return;
629 }
630
631 // Send AJAX request
632 $.ajax({
633 url: kngPreloaderAdmin.ajaxUrl,
634 type: 'POST',
635 data: {
636 action: 'king_addons_preloader_delete_rule',
637 nonce: kngPreloaderAdmin.nonce,
638 rule_id: ruleId
639 },
640 success: function (response) {
641 if (response.success) {
642 $rule.slideUp(200, function () {
643 $(this).remove();
644 KngPreloaderAdmin.checkEmptyRules();
645 KngPreloaderAdmin.updateRuleIndices();
646 });
647 } else {
648 alert(response.data?.message || kngPreloaderAdmin.strings.error);
649 }
650 },
651 error: function () {
652 alert(kngPreloaderAdmin.strings.error);
653 }
654 });
655 },
656
657 /**
658 * Cancel rule edit
659 */
660 cancelRuleEdit: function ($rule) {
661 const ruleId = $rule.attr('data-rule-id');
662
663 // If it's a new unsaved rule, remove it
664 if (ruleId && ruleId.startsWith('new_')) {
665 $rule.slideUp(200, function () {
666 $(this).remove();
667 KngPreloaderAdmin.checkEmptyRules();
668 });
669 } else {
670 // Just hide details for existing rules
671 $rule.find('.ka-preloader-rule-item__details').slideUp(200);
672 }
673 },
674
675 /**
676 * Check if rules list is empty and show empty state
677 */
678 checkEmptyRules: function () {
679 const $rulesList = $('#ka-rules-list');
680 const rulesCount = $rulesList.find('.ka-preloader-rule-item').length;
681
682 if (rulesCount === 0) {
683 $('#ka-rules-empty').show();
684 }
685 },
686
687 /**
688 * Add new rule
689 */
690 addNewRule: function () {
691 // Get rules list, try ID first then class
692 let $rulesList = $('#ka-rules-list');
693 if (!$rulesList.length) {
694 $rulesList = this.elements.rulesList;
695 }
696
697 if (!$rulesList.length) {
698 console.error('Rules list container not found');
699 return;
700 }
701
702 // Hide empty state
703 $('#ka-rules-empty').hide();
704
705 // Try to use template from PHP first
706 const $template = $('#ka-rule-template');
707 let $newRule;
708
709 if ($template.length) {
710 // Clone template content
711 $newRule = $($template.html()).clone();
712 $newRule.attr('data-rule-id', 'new_' + Date.now());
713 $newRule.find('.ka-preloader-rule-item__details').show();
714 } else {
715 // Fallback to JS-generated HTML
716 const ruleIndex = $rulesList.find('.ka-preloader-rule-item').length;
717 $newRule = $(`
718 <div class="ka-preloader-rule-item expanded" data-index="${ruleIndex}">
719 <div class="ka-preloader-rule-header">
720 <span class="ka-preloader-rule-title">New Rule</span>
721 <div class="ka-preloader-rule-actions">
722 <button type="button" class="ka-preloader-rule-remove">
723 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
724 <path d="M18 6L6 18M6 6l12 12"/>
725 </svg>
726 </button>
727 </div>
728 </div>
729 <div class="ka-preloader-rule-content">
730 <div class="ka-preloader-rule-field">
731 <label>Rule Name</label>
732 <input type="text" name="rules[${ruleIndex}][name]" value="New Rule" class="ka-preloader-rule-name">
733 </div>
734 <div class="ka-preloader-rule-field">
735 <label>Rule Type</label>
736 <select name="rules[${ruleIndex}][type]" class="ka-preloader-rule-type">
737 <option value="include">Show On (Include)</option>
738 <option value="exclude">Hide On (Exclude)</option>
739 </select>
740 </div>
741 <div class="ka-preloader-rule-field">
742 <label>Condition</label>
743 <select name="rules[${ruleIndex}][condition]" class="ka-preloader-rule-condition">
744 <option value="entire_site">Entire Site</option>
745 <option value="front_page">Front Page</option>
746 <option value="blog_page">Blog Page</option>
747 <option value="posts">All Posts</option>
748 <option value="pages">All Pages</option>
749 <option value="archives">Archives</option>
750 <option value="search">Search Results</option>
751 <option value="404">404 Page</option>
752 <option value="specific_page">Specific Page</option>
753 <option value="specific_post">Specific Post</option>
754 </select>
755 </div>
756 <div class="ka-preloader-rule-field ka-preloader-rule-value-field" style="display: none;">
757 <label>Value</label>
758 <input type="text" name="rules[${ruleIndex}][value]" class="ka-preloader-rule-value" placeholder="Enter ID or slug">
759 </div>
760 <div class="ka-preloader-rule-field">
761 <label>
762 <input type="checkbox" name="rules[${ruleIndex}][enabled]" value="1" checked>
763 Enable this rule
764 </label>
765 </div>
766 </div>
767 </div>
768 `);
769 }
770
771 $rulesList.append($newRule);
772
773 // Scroll to new rule
774 if ($newRule && $newRule[0]) {
775 $newRule[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
776 }
777 },
778
779 /**
780 * Update rule conditions based on type
781 */
782 updateRuleConditions: function ($rule, condition) {
783 // Find value field (support both class naming conventions)
784 const $valueField = $rule.find('.ka-preloader-rule-value-field');
785 const $conditionValueRow = $rule.find('.ka-row').filter(function() {
786 return $(this).find('.ka-rule-condition-value').length > 0;
787 });
788
789 // Conditions that need a value input
790 const needsValue = ['url_contains', 'url_equals', 'url_regex', 'query_param', 'specific_page', 'specific_post', 'specific_cpt', 'post_type', 'page_template'];
791
792 if (needsValue.includes(condition)) {
793 $valueField.slideDown(200);
794 $conditionValueRow.slideDown(200);
795 } else {
796 $valueField.slideUp(200);
797 $conditionValueRow.slideUp(200);
798 }
799 },
800
801 /**
802 * Update rule indices after deletion
803 */
804 updateRuleIndices: function () {
805 this.elements.rulesList.find('.ka-preloader-rule-item').each(function (index) {
806 const $rule = $(this);
807 $rule.attr('data-index', index);
808
809 // Update all input names
810 $rule.find('input, select').each(function () {
811 const $field = $(this);
812 const name = $field.attr('name');
813 if (name) {
814 $field.attr('name', name.replace(/\[\d+\]/, '[' + index + ']'));
815 }
816 });
817 });
818 },
819
820 /**
821 * Initialize import/export
822 */
823 initImportExport: function () {
824 // Export button
825 $(document).on('click', '.ka-preloader-export-btn', function (e) {
826 e.preventDefault();
827 KngPreloaderAdmin.exportSettings();
828 });
829
830 // Import button
831 $(document).on('click', '.ka-preloader-import-btn', function (e) {
832 e.preventDefault();
833 KngPreloaderAdmin.importSettings();
834 });
835
836 // Copy to clipboard
837 $(document).on('click', '.ka-preloader-copy-btn', function (e) {
838 e.preventDefault();
839 const $textarea = $(this).siblings('textarea');
840 $textarea.select();
841 document.execCommand('copy');
842
843 const $btn = $(this);
844 const originalText = $btn.text();
845 $btn.text('Copied!');
846 setTimeout(() => $btn.text(originalText), 2000);
847 });
848 },
849
850 /**
851 * Export settings
852 */
853 exportSettings: function () {
854 const settings = this.collectFormSettings();
855 const exportData = JSON.stringify(settings, null, 2);
856
857 const $exportArea = $('.ka-preloader-export-area');
858 $exportArea.val(exportData).show();
859 },
860
861 /**
862 * Import settings
863 */
864 importSettings: function () {
865 const $importArea = $('.ka-preloader-import-area');
866 const importData = $importArea.val().trim();
867
868 if (!importData) {
869 alert('Please paste settings JSON to import.');
870 return;
871 }
872
873 try {
874 const settings = JSON.parse(importData);
875 this.applyImportedSettings(settings);
876 alert('Settings imported successfully! Please save to apply.');
877 } catch (e) {
878 alert('Invalid JSON format. Please check your import data.');
879 }
880 },
881
882 /**
883 * Apply imported settings to form
884 */
885 applyImportedSettings: function (settings) {
886 for (const [key, value] of Object.entries(settings)) {
887 const $field = this.elements.form.find('[name="' + key + '"]');
888
889 if (!$field.length) continue;
890
891 if ($field.is(':checkbox')) {
892 $field.prop('checked', value === '1');
893 } else if ($field.is(':radio')) {
894 $field.filter('[value="' + value + '"]').prop('checked', true);
895 } else if ($field.is('select')) {
896 $field.val(value);
897 } else {
898 $field.val(value);
899 }
900 }
901
902 // Update preview
903 this.updatePreview();
904
905 // Update preset card selection
906 const preset = settings.preset;
907 if (preset) {
908 $('.ka-preloader-preset-card').removeClass('active');
909 $('.ka-preloader-preset-card[data-preset="' + preset + '"]').addClass('active');
910 }
911 },
912
913 /**
914 * Initialize dark mode toggle
915 */
916 initDarkModeToggle: function () {
917 $(document).on('change', '.ka-preloader-dark-toggle input', function () {
918 const isDark = $(this).is(':checked');
919
920 if (isDark) {
921 $('.ka-preloader-admin-page').addClass('dark-mode');
922 localStorage.setItem('kng_preloader_dark_mode', '1');
923 } else {
924 $('.ka-preloader-admin-page').removeClass('dark-mode');
925 localStorage.setItem('kng_preloader_dark_mode', '0');
926 }
927 });
928
929 // Restore preference
930 const savedDarkMode = localStorage.getItem('kng_preloader_dark_mode');
931 if (savedDarkMode === '1') {
932 $('.ka-preloader-dark-toggle input').prop('checked', true).trigger('change');
933 }
934 },
935
936 /**
937 * Initialize form submit
938 */
939 initFormSubmit: function () {
940 this.elements.form.on('submit', function (e) {
941 // Let the form submit naturally, just show a loading state
942 const $submitBtn = $(this).find('button[type="submit"]');
943 $submitBtn.prop('disabled', true).text('Saving...');
944 });
945 },
946
947 /**
948 * Initialize preview modal
949 */
950 initPreviewModal: function () {
951 $(document).on('click', '.ka-preloader-full-preview-btn', function (e) {
952 e.preventDefault();
953 KngPreloaderAdmin.openPreviewModal();
954 });
955
956 $(document).on('click', '.ka-preloader-modal-close, .ka-preloader-modal-backdrop', function (e) {
957 e.preventDefault();
958 KngPreloaderAdmin.closePreviewModal();
959 });
960
961 // Close on Escape key
962 $(document).on('keydown', function (e) {
963 if (e.key === 'Escape') {
964 KngPreloaderAdmin.closePreviewModal();
965 }
966 });
967 },
968
969 /**
970 * Open preview modal
971 */
972 openPreviewModal: function () {
973 const settings = this.collectFormSettings();
974 const $modal = $('.ka-preloader-preview-modal');
975
976 if (!$modal.length) {
977 // Create modal if not exists
978 const modalHtml = `
979 <div class="ka-preloader-preview-modal">
980 <div class="ka-preloader-modal-backdrop"></div>
981 <div class="ka-preloader-modal-content">
982 <button class="ka-preloader-modal-close">&times;</button>
983 <div class="ka-preloader-modal-preview"></div>
984 </div>
985 </div>
986 `;
987 $('body').append(modalHtml);
988 }
989
990 // Build preview HTML
991 const presetHtml = this.getPresetHtml(settings.preset || 'spinner-circle');
992 const previewHtml = `
993 <div class="kng-site-preloader" style="
994 --kng-preloader-accent: ${settings.accent_color || '#0071e3'};
995 --kng-preloader-bg: ${settings.background_color || '#ffffff'};
996 --kng-preloader-text: ${settings.text_color || '#1d1d1f'};
997 --kng-preloader-size: ${settings.animation_size || 48}px;
998 ">
999 <div class="kng-site-preloader__overlay" style="background: ${settings.background_color || '#ffffff'}"></div>
1000 <div class="kng-site-preloader__content">
1001 <div class="kng-site-preloader__animation">${presetHtml}</div>
1002 ${settings.text_content ? '<div class="kng-site-preloader__text">' + settings.text_content + '</div>' : ''}
1003 </div>
1004 </div>
1005 `;
1006
1007 $('.ka-preloader-modal-preview').html(previewHtml);
1008 $('.ka-preloader-preview-modal').addClass('active');
1009 $('body').addClass('ka-preloader-modal-open');
1010 },
1011
1012 /**
1013 * Close preview modal
1014 */
1015 closePreviewModal: function () {
1016 $('.ka-preloader-preview-modal').removeClass('active');
1017 $('body').removeClass('ka-preloader-modal-open');
1018 },
1019
1020 /**
1021 * Debounce helper
1022 */
1023 debounce: function (func, wait) {
1024 let timeout;
1025 return function executedFunction(...args) {
1026 const later = () => {
1027 clearTimeout(timeout);
1028 func.apply(KngPreloaderAdmin, args);
1029 };
1030 clearTimeout(timeout);
1031 timeout = setTimeout(later, wait);
1032 };
1033 }
1034 };
1035
1036 // Initialize on DOM ready
1037 $(document).ready(function () {
1038 KngPreloaderAdmin.init();
1039 });
1040
1041 // Expose globally for debugging
1042 window.KngPreloaderAdmin = KngPreloaderAdmin;
1043
1044 })(jQuery);
1045