PluginProbe ʕ •ᴥ•ʔ
Flex Import / 3.0
Flex Import v3.0
3.0 2.9 trunk 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8
flex-import / assets / js / template-admin.js
flex-import / assets / js Last commit date
admin-script.js 1 week ago dashboard-posts.js 1 week ago free-template-admin.js 1 week ago front-script.js 1 week ago jquery.notify.min.js 1 week ago license_script.js 1 week ago plugin-admin.js 1 week ago template-admin.js 1 week ago
template-admin.js
1134 lines
1 /**
2 * Updates the progress bar and text during the import process.
3 * @param {number} percent - The percentage of progress (0-100).
4 * @param {string} message - The message to display with the progress.
5 */
6 function updateProgress(percent, message) {
7 const progressBar = document.getElementById('import-process-progress-bar');
8 const progressText = document.getElementById('import-process-progress-text');
9 if (progressBar && progressText) {
10 progressBar.style.width = `${percent}%`;
11 progressText.textContent = `${message} (${percent}%)`;
12 }
13 }
14
15 function getTemplateData(template) {
16
17 let pluginsData = template.metafields?.selected_plugins || {};
18
19 // If pluginsData is array (wrong structure), try to recover
20 if (Array.isArray(pluginsData)) {
21 console.warn('selected_plugins is array, expected object');
22 pluginsData = {};
23 }
24
25 return {
26 textDomain: template.template_domain,
27 jsonPath: template.metafields?.json_path || '',
28 contentPath: template.metafields?.content_path || '',
29 templateCss: template.metafields?.template_css_path || '',
30 templateJs: template.metafields?.template_js_path || '',
31 templatePhp: template.metafields?.template_php_path || '',
32 plugins: JSON.stringify(pluginsData),
33 templateMobileImage: template.metafields?.template_screenshot || '',
34 templateResponsiveImage: template.metafields?.template_responsive_image || '',
35 title: template.title || '',
36 };
37 }
38
39 /**
40 * Sets data attributes on a button element for template import.
41 * @param {HTMLElement} button - The button element.
42 * @param {Object} template - The template object.
43 */
44 function setButtonDataAttributes(button, template) {
45 const data = getTemplateData(template);
46 button.setAttribute('data-text-domain', data.textDomain);
47 button.setAttribute('data-json-path', data.jsonPath);
48 button.setAttribute('data-content-path', data.contentPath);
49 button.setAttribute('data-template-php', data.templatePhp);
50 button.setAttribute('data-template-css', data.templateCss);
51 button.setAttribute('data-template-js', data.templateJs);
52 button.setAttribute('data-plugins', data.plugins);
53 button.setAttribute('data-template-mobile-image', data.templateMobileImage);
54 button.setAttribute('data-template-responsive-image', data.templateResponsiveImage);
55 button.setAttribute('data-title', data.title);
56 }
57
58 /**
59 * Initializes the template admin functionality on DOM content loaded.
60 * Handles template fetching, display, pagination, search, and import processes.
61 */
62 document.addEventListener('DOMContentLoaded', function () {
63 let currentPage = 1;
64 let totalPages = 1;
65 let currentCategoryId = null;
66 let currentCategorySlug = null;
67 let currentSearchQuery = null;
68 let overflowTemplate = null;
69 const templatesApiEndpoint = fleximp_template_ajax_object.apiEndpoint;
70 let isFetchingTemplates = false;
71 let isFetchingCategories = false;
72 let hideLoaderTimeout = null;
73
74 function showLoader(num = 6) {
75 const loader = document.getElementById('templates-skeleton-loader');
76 const templatesListContainer = document.getElementById('templates-list');
77 if (templatesListContainer) {
78 templatesListContainer.innerHTML = '';
79 }
80 if (loader) {
81 const skeletonGrid = loader.querySelector('.skeleton-grid');
82 if (skeletonGrid) {
83 skeletonGrid.innerHTML = '';
84 for (let i = 0; i < num; i++) {
85 const skeletonItem = document.createElement('div');
86 skeletonItem.className = 'skeleton-item';
87 skeletonItem.innerHTML = `
88 <div class="skeleton-image"></div>
89 <div class="skeleton-content">
90 <div class="skeleton-title"></div>
91 <div class="skeleton-buttons">
92 <div class="skeleton-button"></div>
93 <div class="skeleton-button"></div>
94 </div>
95 </div>
96 `;
97 skeletonGrid.appendChild(skeletonItem);
98 }
99 }
100 loader.style.display = 'block';
101 }
102 }
103
104 function hideLoader() {
105 const loader = document.getElementById('templates-skeleton-loader');
106 if (loader) loader.style.display = 'none';
107 }
108
109 /**
110 * Checks if the loader is currently visible.
111 * @returns {boolean} True if loader is visible, false otherwise.
112 */
113 function isLoaderVisible() {
114 const loader = document.getElementById('templates-skeleton-loader');
115 return loader && loader.style.display === 'block';
116 }
117
118 /**
119 * Handles the confirm button click to set the text domain and initiate import.
120 * Stores import data in sessionStorage and reloads the page for auto-continue.
121 */
122 function ensureElementor() {
123 if (fleximp_template_ajax_object.isElementorActive === '1') {
124 return Promise.resolve();
125 }
126 return fetch(fleximp_template_ajax_object.ajax_url, {
127 method: 'POST',
128 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
129 body: new URLSearchParams({
130 action: 'fleximp_checked_install_plugin',
131 plugin_slug: 'elementor',
132 plugin_name: 'Elementor',
133 plugin_main_file: 'elementor.php',
134 }),
135 })
136 .then(r => r.json())
137 .then(resp => {
138 if (!resp.success) {
139 throw new Error(resp.data?.message || 'Failed to install Elementor.');
140 }
141 });
142 }
143
144 function handleConfirmClick() {
145 const textDomain = this.getAttribute('data-text-domain');
146 const jsonPath = this.getAttribute('data-json-path');
147 const contentPath = this.getAttribute('data-content-path');
148 const templateCss = this.getAttribute('data-template-css');
149 const templateJs = this.getAttribute('data-template-js');
150 const templatePhp = this.getAttribute('data-template-php');
151 const plugins = this.getAttribute('data-plugins');
152 const templateResponsiveImage = this.getAttribute('data-template-responsive-image');
153 const templateMobileImage = this.getAttribute('data-template-mobile-image');
154 const templateTitle = this.getAttribute('data-title');
155
156 const data = {
157 textDomain,
158 jsonPath,
159 contentPath,
160 templateCss,
161 templateJs,
162 templatePhp,
163 plugins,
164 templateResponsiveImage,
165 templateMobileImage,
166 title: templateTitle,
167 };
168
169 sessionStorage.setItem('fleximp_import_data', JSON.stringify(data));
170
171 if (!textDomain) {
172 alert('Text domain not found.');
173 return;
174 }
175
176 ensureElementor()
177 .then(() => fetch(fleximp_ajax_object.ajax_url, {
178 method: 'POST',
179 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
180 body: new URLSearchParams({
181 action: 'fleximp_set_text_domain',
182 text_domain: textDomain,
183 template_php: templatePhp,
184 template_mobile_image: templateMobileImage,
185 template_title: templateTitle
186 })
187 }))
188 .then(res => res.json())
189 .then(response => {
190 if (response.success) {
191 sessionStorage.setItem('fleximp_auto_continue_pro', '1');
192 location.reload();
193 } else {
194 alert('Failed to update text domain.');
195 }
196 })
197 .catch(error => {
198 console.error('Import error:', error);
199 alert('Error: ' + error.message);
200 });
201 }
202
203 /**
204 * Displays the import process UI for a matching template.
205 * Sets up the import section, buttons, and data attributes.
206 * @param {Object} matchingTemplate - The template object to import.
207 */
208 function showImportProcess(matchingTemplate) {
209 // if (!isLoaderVisible()) {
210 // showLoader();
211 // }
212
213 const importSection = document.querySelector('#templates .flex-templates-import-process');
214 const importTitle = document.getElementById('import-process-title');
215 const importDescription = document.getElementById('import-process-description');
216 const importMobileTitle = document.getElementById('import-process-mobile-title');
217 const importMobileImage = document.getElementById('import-process-mobile-image');
218 const importScreenshot = document.getElementById('import-process-mid-image');
219 const importResponsiveImage = document.getElementById('import-process-responsive-image');
220
221 if (importSection && importScreenshot && importTitle && importMobileTitle && importMobileImage && importResponsiveImage) {
222 importTitle.textContent = matchingTemplate.title;
223 importDescription.textContent = matchingTemplate.metafields?.template_desc || '';
224 importMobileTitle.textContent = matchingTemplate.title;
225 importMobileImage.src = matchingTemplate.metafields?.template_screenshot || '';
226 importScreenshot.src = matchingTemplate.metafields?.template_screenshot || '';
227 importResponsiveImage.src = matchingTemplate.metafields?.template_responsive_image || '';
228 importSection.style.display = 'block';
229 importSection.scrollIntoView({ behavior: 'smooth' });
230
231 const confirmBtn = document.getElementById('import-process-confirm-btn');
232 if (confirmBtn) {
233 setButtonDataAttributes(confirmBtn, matchingTemplate);
234 sessionStorage.setItem('fleximp_import_data', JSON.stringify(getTemplateData(matchingTemplate)));
235 confirmBtn.addEventListener('click', handleConfirmClick, { once: true });
236 }
237
238 const cancelBtn = document.getElementById('import-process-cancel-btn');
239 if (cancelBtn) {
240 cancelBtn.addEventListener('click', () => {
241 importSection.style.display = 'none';
242 });
243 }
244 const demoUrl = matchingTemplate.metafields?.demo_url || '#';
245 const docUrl = matchingTemplate.metafields?.documentation_url || '';
246 const buyNow = matchingTemplate.metafields?.buy_now_url || '#';
247 const isUserPremium = fleximp_template_ajax_object.isPremiumUser;
248 const upgradeBtn = document.getElementById('upgrade-pro-btn');
249 if (isUserPremium) {
250 upgradeBtn.style.display = 'none';
251 } else {
252 upgradeBtn.style.display = 'inline-block';
253 }
254 upgradeBtn.href = buyNow;
255 if (docUrl) {
256 const liveDocBtn = document.getElementById('document-url-btn');
257 liveDocBtn.style.display = 'inline-block';
258 }
259 else {
260 const liveDocBtn = document.getElementById('document-url-btn');
261 liveDocBtn.style.display = 'none';
262 }
263 const liveDemoBtn = document.getElementById('live-demo-btn');
264 liveDemoBtn.href = demoUrl;
265 const liveDocBtn = document.getElementById('document-url-btn');
266 liveDocBtn.href = docUrl;
267 setTimeout(() => {
268 if (isLoaderVisible()) {
269 hideLoader();
270 }
271 }, 300);
272 }
273 }
274
275 /**
276 * Displays the done process UI after a successful import.
277 * Shows the uninstall button with necessary data attributes.
278 * @param {Object} importedTemplateObj - The imported template object.
279 */
280 function showDoneProcess() {
281 const importSection = document.querySelector('#templates .flex-templates-import-process');
282 const uninstallBtn = document.getElementById('import-process-uninstall-btn');
283 if (importSection && uninstallBtn) {
284 importSection.style.display = 'block';
285 importSection.scrollIntoView({ behavior: 'smooth' });
286 document.getElementById('import-process-confirm').style.display = 'none';
287 document.getElementById('import-process-plugins').style.display = 'none';
288 document.getElementById('import-process-done').style.display = 'block';
289 uninstallBtn.style.display = 'inline-block';
290 setTimeout(() => {
291 if (isLoaderVisible()) {
292 hideLoader();
293 }
294 }, 400);
295 }
296 }
297
298 /**
299 * Fetches templates from the API based on category, page, and search query.
300 * Updates the UI with fetched templates and pagination controls.
301 * @param {number|null} categoryId - The category ID to filter by.
302 * @param {string|null} categorySlug - The category slug.
303 * @param {number} page - The page number to fetch.
304 * @param {string|null} searchQuery - The search query string.
305 */
306 function fetchTemplates(categoryId = null, categorySlug = null, page = 1, searchQuery = null) {
307 if (isFetchingTemplates) return;
308 isFetchingTemplates = true;
309 showLoader();
310
311 if (categoryId && categorySlug) {
312 currentCategoryId = categoryId;
313 currentCategorySlug = categorySlug;
314 }
315 currentPage = page;
316 currentSearchQuery = searchQuery;
317
318 const themeAuthor =
319 fleximp_template_ajax_object.active_theme_author?.toLowerCase() || "";
320 const isFlexTheme = themeAuthor === "flextheme";
321
322 // const currentTextDomain = fleximp_template_ajax_object.fleximp_dynamic_text_domain || fleximp_template_ajax_object.fleximp_current_text_domain;
323 const currentTextDomain = fleximp_template_ajax_object.fleximp_current_text_domain;
324
325 const requestBody = {
326 action: "getTemplates",
327 per_page: 6,
328 page: currentPage,
329 free_pro: "Pro"
330 };
331
332 if (isFlexTheme) {
333 requestBody.free_theme_domain = currentTextDomain;
334 } else {
335 requestBody.free_theme_domain = "";
336 }
337
338 if (currentCategoryId && currentCategorySlug) {
339 requestBody.product_cat = currentCategoryId;
340 } else {
341 requestBody.product_cat = 15; // All
342 }
343
344 if (searchQuery) {
345 requestBody.search = searchQuery;
346 }
347
348 fetch(templatesApiEndpoint, {
349 method: 'POST',
350 headers: { 'Content-Type': 'application/json' },
351 body: JSON.stringify(requestBody)
352 })
353 .then(response => response.json())
354 .then(async data => {
355 if (data.success && data.data) {
356 let templates = data.data.posts || data.data.data?.posts;
357 if (Array.isArray(templates)) {
358 // hideLoader();
359 // Keep only Pro templates as a client-side safety guard
360 templates = templates.filter(t => t.metafields?.free_pro === 'Pro');
361 const freeTemplate = data?.data?.data?.free_template || data?.data?.free_template || data?.free_template || null;
362 await displayTemplates(templates, freeTemplate);
363 totalPages = data.data.total_pages || (data.data.data ? data.data.data.total_pages : 1);
364 updatePaginationControls();
365 } else {
366 console.error('Invalid templates data format:', data);
367 await displayTemplates([], null);
368 }
369 } else {
370 console.error('API Error:', data.message || 'Unknown error');
371 await displayTemplates([], null);
372 }
373 isFetchingTemplates = false;
374 })
375 .catch(async error => {
376 console.error('Network Error:', error);
377 await displayTemplates([], null);
378 isFetchingTemplates = false;
379 });
380 }
381
382 /**
383 * Updates the pagination controls based on current page and total pages.
384 * Creates previous, page number, and next buttons with ellipsis for large page counts.
385 */
386 function updatePaginationControls() {
387 const paginationContainer = document.getElementById('pagination-controls');
388 if (!paginationContainer) {
389 console.error('Pagination container not found!');
390 return;
391 }
392
393 paginationContainer.innerHTML = '';
394
395 const prevLi = document.createElement('li');
396 prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
397 prevLi.innerHTML = `<a class="page-link" href="#" aria-label="Previous">
398 <span aria-hidden="true"><i class="fa-solid fa-angle-left" style="color: hsl(0, 0%, 100%);"></i></span>
399 </a>`;
400 prevLi.addEventListener('click', (e) => {
401 e.preventDefault();
402 if (currentPage > 1) {
403 fetchTemplates(currentCategoryId, currentCategorySlug, currentPage - 1, currentSearchQuery);
404 }
405 });
406 paginationContainer.appendChild(prevLi);
407
408 // Page numbers with ellipsis logic
409 const maxVisiblePages = 6;
410 let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
411 let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
412
413 if (endPage - startPage + 1 < maxVisiblePages) {
414 startPage = Math.max(1, endPage - maxVisiblePages + 1);
415 }
416
417 // First page and ellipsis if needed
418 if (startPage > 1) {
419 const firstLi = document.createElement('li');
420 firstLi.className = 'page-item';
421 firstLi.innerHTML = `<a class="page-link" href="#">1</a>`;
422 firstLi.addEventListener('click', (e) => {
423 e.preventDefault();
424 fetchTemplates(currentCategoryId, currentCategorySlug, 1, currentSearchQuery);
425 });
426 paginationContainer.appendChild(firstLi);
427
428 if (startPage > 2) {
429 const ellipsisLi = document.createElement('li');
430 ellipsisLi.className = 'page-item disabled';
431 ellipsisLi.innerHTML = `<span class="page-link">...</span>`;
432 paginationContainer.appendChild(ellipsisLi);
433 }
434 }
435
436 // Visible page numbers
437 for (let i = startPage; i <= endPage; i++) {
438 const pageLi = document.createElement('li');
439 pageLi.className = `page-item ${i === currentPage ? 'active' : ''}`;
440 pageLi.innerHTML = `<a class="page-link" href="#">${i}</a>`;
441 pageLi.addEventListener('click', (e) => {
442 e.preventDefault();
443 fetchTemplates(currentCategoryId, currentCategorySlug, i, currentSearchQuery);
444 });
445 paginationContainer.appendChild(pageLi);
446 }
447
448 // Last page and ellipsis if needed
449 if (endPage < totalPages) {
450 if (endPage < totalPages - 1) {
451 const ellipsisLi = document.createElement('li');
452 ellipsisLi.className = 'page-item disabled';
453 ellipsisLi.innerHTML = `<span class="page-link">...</span>`;
454 paginationContainer.appendChild(ellipsisLi);
455 }
456
457 const lastLi = document.createElement('li');
458 lastLi.className = 'page-item';
459 lastLi.innerHTML = `<a class="page-link" href="#">${totalPages}</a>`;
460 lastLi.addEventListener('click', (e) => {
461 e.preventDefault();
462 fetchTemplates(currentCategoryId, currentCategorySlug, totalPages);
463 });
464 paginationContainer.appendChild(lastLi);
465 }
466
467 // Next button
468 const nextLi = document.createElement('li');
469 nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
470 nextLi.innerHTML = `<a class="page-link" href="#" aria-label="Next">
471 <span aria-hidden="true"><i class="fa-solid fa-angle-right" style="color: hsl(0, 0%, 100%);"></i></span>
472 </a>`;
473 nextLi.addEventListener('click', (e) => {
474 e.preventDefault();
475 if (currentPage < totalPages) {
476 fetchTemplates(currentCategoryId, currentCategorySlug, currentPage + 1);
477 }
478 });
479 paginationContainer.appendChild(nextLi);
480 }
481 // end
482
483 /**
484 * Fetches categories from the API and displays them in the UI.
485 */
486 function fetchCategories() {
487 if (isFetchingCategories) return;
488 isFetchingCategories = true;
489
490 fetch(templatesApiEndpoint, {
491 method: 'POST',
492 headers: { 'Content-Type': 'application/json' },
493 body: JSON.stringify({ action: 'getCategories' })
494 })
495 .then(response => response.json())
496 .then(data => {
497 if (data.success && Array.isArray(data.data.data)) {
498 displayCategories(data.data.data);
499 } else {
500 console.error('Error fetching categories:', data);
501 }
502 })
503 .catch(error => {
504 console.error('Error fetching categories:', error);
505 })
506 .finally(() => {
507 isFetchingCategories = false;
508 });
509 }
510
511
512 /**
513 * Displays the list of templates in the UI.
514 * Handles premium/free badges, import/uninstall buttons, and auto-shows import process if needed.
515 * @param {Array} templates - Array of template objects to display.
516 * @param {Object|null} freeTemplate - Free template object from API response.
517 */
518 async function displayTemplates(templates, freeTemplate = null) {
519
520 if (typeof freeTemplate === 'undefined') freeTemplate = null;
521
522 const templatesListContainer = document.getElementById('templates-list');
523 templatesListContainer.innerHTML = '';
524
525 const activeImportedTemplate = fleximp_template_ajax_object.imported_template;
526 const activeThemeTextDomain = fleximp_template_ajax_object.fleximp_current_text_domain;
527 // let filteredTemplates = templates.filter(template => {
528 // return template.template_domain !== activeImportedTemplate;
529 // });
530
531 let filteredTemplates = templates.filter(template => {
532 return (
533 template.template_domain !== activeImportedTemplate &&
534 template.template_domain !== activeThemeTextDomain
535 );
536 });
537
538
539 if (!filteredTemplates.length) {
540 templatesListContainer.innerHTML = `
541 <div class="no-templates-message">
542 No templates found
543 </div>`;
544 hideLoader();
545 if (activeImportedTemplate && activeImportedTemplate === activeThemeTextDomain) {
546 showDoneProcess();
547 } else if (freeTemplate) {
548 showImportProcess(freeTemplate);
549 }
550 return;
551 }
552 // end
553
554 filteredTemplates.forEach(template => {
555 const templateSlug = template.title.toLowerCase().replace(/ /g, '-');
556 const demoUrl = template.metafields?.demo_url || '#';
557 const buyUrl = template.metafields?.buy_now_url || '#';
558 const selectedPlugins = template.metafields?.selected_plugins || [];
559 const jsonPath = template.metafields?.json_path || '';
560 const templateMainImage = template.metafields?.template_main_image || '';
561 const templateDesc = template.metafields?.template_desc || '';
562 const templateDocument = template.metafields?.documentation_url || '';
563 const templateMobileImage = template.metafields?.template_screenshot || '';
564 const templateResponsiveImage = template.metafields?.template_responsive_image || '';
565 const contentPath = template.metafields?.content_path || '';
566 const textDomain = template.template_domain || '';
567 const templateCss = template.metafields?.template_css_path || '';
568 const templateJs = template.metafields?.template_js_path || '';
569 const templatePhp = template.metafields?.template_php_path || '';
570 const flexthemebuynow = template.metafields?.buy_now_url || '';
571 const flexthemeprice = template.metafields?.template_price || '';
572 const fleximpCurrentTextDomain = fleximp_template_ajax_object.fleximp_dynamic_text_domain;
573 const isBundleUser = fleximp_template_ajax_object.is_Bundle === true || fleximp_template_ajax_object.is_Bundle === 'true';
574 let buttonHTML = '';
575
576 const isPremium = template.metafields?.free_pro === 'Pro';
577 const isUserPremium = fleximp_template_ajax_object.isPremiumUser;
578 const isImported = fleximp_template_ajax_object.imported_template;
579 const fleximpCurrentThemeDomain = fleximp_template_ajax_object.fleximp_current_text_domain;
580
581 if (isPremium && textDomain !== fleximpCurrentThemeDomain) {
582
583 if (isBundleUser) {
584 buttonHTML = `
585 <a href="${fleximp_template_ajax_object.themes_redirect_page_url}"
586 class="btn flex-template-buttons flex-temp-btn try-now-btn" target="_blank"
587 rel="noopener noreferrer">
588 TRY NOW
589 </a>`;
590 } else {
591 buttonHTML = `
592 <a href="${flexthemebuynow}"
593 class="btn flex-template-buttons flex-temp-btn buy-now-btn"
594 target="_blank"
595 rel="noopener noreferrer">
596 BUY NOW
597 </a>`;
598 }
599 }
600
601 else {
602
603 if (isPremium && !isUserPremium) {
604
605 buttonHTML = `
606 <a href="https://www.flextheme.net/products/flex-pro-wordpress-theme"
607 target="_blank"
608 class="btn flex-template-buttons flex-temp-btn get-pro-btn">
609 UPGRADE TO FLEX PRO
610 </a>`;
611
612 } else {
613
614 if (isImported === textDomain) {
615
616 buttonHTML = `
617 <button class="btn flex-template-buttons uninstall-btn"
618 data-template="${templateSlug}"
619 data-title="${template.title}"
620 data-plugins='${JSON.stringify(selectedPlugins)}'
621 data-json-path="${jsonPath}"
622 data-template-main-image="${templateMainImage}"
623 data-template-mobile-image="${templateMobileImage}"
624 data-template-responsive-image="${templateResponsiveImage}"
625 data-template-description="${templateDesc}"
626 data-template-doc-url="${templateDocument}"
627 data-content-path="${contentPath}"
628 data-text-domain="${textDomain}"
629 data-template-css="${templateCss}"
630 data-template-js="${templateJs}"
631 data-template-php="${templatePhp}">
632 Uninstall
633 </button>`;
634
635 } else {
636
637 const isDisabled =
638 fleximpCurrentTextDomain !== textDomain && fleximpCurrentTextDomain !== ''
639 ? 'disabled title="first uninstall imported template"'
640 : '';
641
642 buttonHTML = `
643 <button class="btn flex-unique-import-button flex-template-buttons import-btn"
644 ${isDisabled}
645 data-template="${templateSlug}"
646 data-title="${template.title}"
647 data-plugins='${JSON.stringify(selectedPlugins)}'
648 data-json-path="${jsonPath}"
649 data-template-main-image="${templateMainImage}"
650 data-template-mobile-image="${templateMobileImage}"
651 data-template-responsive-image="${templateResponsiveImage}"
652 data-template-description="${templateDesc}"
653 data-template-doc-url="${templateDocument}"
654 data-content-path="${contentPath}"
655 data-text-domain="${textDomain}"
656 data-template-css="${templateCss}"
657 data-template-js="${templateJs}"
658 data-template-php="${templatePhp}"
659 data-demo-url="${demoUrl}"
660 data-template-buy-url="${buyUrl}">
661 IMPORT DEMO
662 </button>`;
663
664 }
665 }
666 }
667
668
669 // end
670 const templateHTML = `
671 <div class="template-item grid-item" style="position: relative;">
672 <div class="flex-imp-temp-inner-box"><img src="${templateMainImage}" alt="${template.title}" style=""></div>
673
674 <div class="fleximp-temp-buttons widgets-btn">
675 <div class="buttons widgets-btn pb-3">
676 <div class="btn-title">
677 <p class="template-title">${template.title}</p>
678 </div>
679 ${isPremium
680 ? `<div class="premium-badge">${flexthemeprice ? `$${flexthemeprice}` : 'Pro'}</div>`
681 : '<div class="premium-badge">Free</div>'
682 }
683 </div>
684 <div class="d-md-flex d-sm-block d-flex flex-gap-div">
685 <a href="${demoUrl}" target="_blank" class="btn flex-template-buttons">LIVE DEMO</a>
686 ${buttonHTML}
687 </div>
688 </div>
689 </div>`;
690
691 templatesListContainer.innerHTML += templateHTML;
692 });
693
694 // Auto-show import process
695 const importedTemplate = fleximp_template_ajax_object.imported_template;
696
697 hideLoader();
698
699 if (importedTemplate && importedTemplate === activeThemeTextDomain) {
700
701 showDoneProcess();
702
703 } else if (freeTemplate) {
704
705 showImportProcess(freeTemplate);
706 }
707 }
708
709 /**
710 * Displays the categories in the select dropdown and loads templates for the first category.
711 * @param {Array} categories - Array of category objects.
712 */
713 function displayCategories(categories) {
714 const categoriesListContainer = document.getElementById('categories-list');
715 categoriesListContainer.innerHTML = '';
716
717 categories.forEach((category, index) => {
718 const option = document.createElement('option');
719 option.value = category.id;
720 option.setAttribute('data-slug', category.slug);
721 option.textContent = category.name;
722 if (index === 0) {
723 option.selected = true;
724 }
725 categoriesListContainer.appendChild(option);
726 });
727
728 // Auto-load templates for the first category
729 if (categories.length > 0) {
730 fetchTemplates(categories[0].id, categories[0].slug);
731 }
732 }
733
734 document.addEventListener('click', function (event) {
735 if (event.target.classList.contains('flex-unique-import-button')) {
736 const templateTitle = event.target.getAttribute('data-title');
737 const templateDesc = event.target.getAttribute('data-template-description');
738 const templateMobileImage = event.target.getAttribute('data-template-mobile-image');
739 const textDomain = event.target.getAttribute('data-text-domain');
740 const jsonPath = event.target.getAttribute('data-json-path');
741 const contentPath = event.target.getAttribute('data-content-path');
742 const templateCss = event.target.getAttribute('data-template-css');
743 const templateJs = event.target.getAttribute('data-template-js');
744 const templatePhp = event.target.getAttribute('data-template-php');
745 const plugins = event.target.getAttribute('data-plugins');
746 const templateResponsiveImage = event.target.getAttribute('data-template-responsive-image');
747 const importSection = document.querySelector('#templates .flex-templates-import-process');
748 const importTitle = document.getElementById('import-process-title');
749 const importDescription = document.getElementById('import-process-description');
750 const importMobileTitle = document.getElementById('import-process-mobile-title');
751 const importMobileImage = document.getElementById('import-process-mobile-image');
752 const importResponsiveImage = document.getElementById('import-process-responsive-image');
753
754 if (importSection && importTitle && importMobileTitle && importMobileImage && importResponsiveImage) {
755 importTitle.textContent = templateTitle;
756 importDescription.textContent = templateDesc;
757 importMobileTitle.textContent = templateTitle;
758 importMobileImage.src = templateMobileImage;
759 importResponsiveImage.src = templateResponsiveImage;
760 importSection.style.display = 'block';
761 importSection.scrollIntoView({ behavior: 'smooth' });
762
763 const demoUrl = event.target.getAttribute('data-demo-url');
764 const buyNow = event.target.getAttribute('data-template-buy-url');
765 const docUrl = event.target.getAttribute('data-template-doc-url') || '';
766 const isUserPremium = fleximp_template_ajax_object.isPremiumUser;
767 const upgradeBtn = document.getElementById('upgrade-pro-btn');
768
769
770
771 if (docUrl) {
772 const liveDocBtn = document.getElementById('document-url-btn');
773 liveDocBtn.style.display = 'inline-block';
774 } else {
775 const liveDocBtn = document.getElementById('document-url-btn');
776 liveDocBtn.style.display = 'none';
777 }
778 if (isUserPremium) {
779 upgradeBtn.style.display = 'none';
780 } else {
781 upgradeBtn.style.display = 'inline-block';
782 }
783 const liveDemoBtn = document.getElementById('live-demo-btn');
784 liveDemoBtn.href = demoUrl;
785 const liveDocBtn = document.getElementById('document-url-btn');
786 liveDocBtn.href = docUrl;
787 const buynowBtn = document.getElementById('upgrade-pro-btn');
788 buynowBtn.href = buyNow;
789 } else {
790 console.error('Import section or elements are missing in the DOM.');
791 }
792
793 const confirmBtn = document.getElementById('import-process-confirm-btn');
794 if (confirmBtn) {
795 confirmBtn.addEventListener('click', function () {
796 const data = {
797 textDomain,
798 jsonPath,
799 contentPath,
800 templateCss,
801 templateJs,
802 templatePhp,
803 plugins,
804 templateResponsiveImage,
805 };
806
807 sessionStorage.setItem('fleximp_import_data', JSON.stringify(data));
808
809 if (!textDomain) {
810 alert('Text domain not found.');
811 return;
812 }
813
814 fetch(fleximp_ajax_object.ajax_url, {
815 method: 'POST',
816 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
817 body: new URLSearchParams({
818 action: 'fleximp_set_text_domain',
819 text_domain: textDomain,
820 template_php: templatePhp,
821 template_mobile_image: templateMobileImage,
822 template_title: templateTitle
823 })
824 })
825 .then(res => res.json())
826 .then(response => {
827 if (response.success) {
828 sessionStorage.setItem('fleximp_auto_continue_pro', '1');
829 location.reload();
830 } else {
831 alert('Failed to update text domain.');
832 }
833 })
834 .catch(error => {
835 console.error('Text domain AJAX error:', error);
836 alert('Error updating text domain.');
837 });
838 }, { once: true });
839 }
840
841 const cancelBtn = document.getElementById('import-process-cancel-btn');
842 if (cancelBtn) {
843 cancelBtn.addEventListener('click', () => {
844 importSection.style.display = 'none';
845 }, { once: true });
846 }
847 }
848 });
849
850 // Initialization function for templates tab
851 let initialised = false;
852
853 function initTemplates() {
854 if (initialised) return;
855 initialised = true;
856 showLoader();
857 fetchCategories();
858 }
859
860 // Check if Templates tab is already active on page load
861 if (jQuery('#templates').hasClass('active') || jQuery('#templates').hasClass('show')) {
862 initTemplates();
863 }
864
865 // Bootstrap 3/4 tab shown event
866 jQuery(document).on('shown.bs.tab', 'a[href="#templates"]', function () {
867 initTemplates();
868 });
869
870 document.getElementById('categories-list').addEventListener('change', function (event) {
871 const selectedOption = event.target.options[event.target.selectedIndex];
872 const categoryId = selectedOption.value;
873 const categorySlug = selectedOption.getAttribute('data-slug');
874
875 fetchTemplates(categoryId, categorySlug, 1, currentSearchQuery);
876 });
877
878 jQuery('body').on('input', '#flex-templates-search', debounce(function (event) {
879 const categoryId = jQuery('#categories-list option:selected').val();
880 const categorySlug = jQuery('#categories-list option:selected').attr('data-slug');
881 const searchQuery = jQuery(this).val();
882 fetchTemplates(categoryId, categorySlug, 1, searchQuery);
883 }, 300));
884
885 });
886
887
888 /**
889 * Debounces a function to limit how often it can be called.
890 * @param {Function} func - The function to debounce.
891 * @param {number} delay - The delay in milliseconds.
892 * @returns {Function} The debounced function.
893 */
894 function debounce(func, delay) {
895 let timeoutId;
896 return function (...args) {
897 clearTimeout(timeoutId);
898 timeoutId = setTimeout(() => func.apply(this, args), delay);
899 };
900 }
901
902 /**
903 * Handles uninstall button clicks for templates.
904 */
905 document.addEventListener('DOMContentLoaded', function () {
906 jQuery(document).on('click', '.uninstall-btn', function () {
907 const button = jQuery(this);
908 const templateSlug = button.data('template');
909 const textDomain = button.data('text-domain');
910
911 if (!confirm('Are you sure you want to uninstall this template and remove all related data?')) {
912 return;
913 }
914
915 jQuery.ajax({
916 url: ajaxurl,
917 type: 'POST',
918 data: {
919 action: 'fleximp_uninstall_template',
920 template_slug: templateSlug,
921 text_domain: textDomain,
922 },
923 success: function (response) {
924 if (response.success) {
925 alert(response.data.message);
926 location.reload();
927 } else {
928 alert('Error: ' + response.data.message);
929 }
930 },
931 });
932 });
933 });
934
935 /**
936 * Handles auto-continue import process after page reload.
937 */
938 window.addEventListener('DOMContentLoaded', function () {
939 if (sessionStorage.getItem('fleximp_auto_continue_pro') === '1') {
940 // Activate templates tab (Pro templates)
941 const templatesTab = document.querySelector('.nav-link[href="#templates"]');
942 if (templatesTab) templatesTab.classList.add('active');
943 const templatesPane = document.querySelector('#templates');
944 if (templatesPane) templatesPane.classList.add('active', 'show');
945
946 // Deactivate other tabs
947 const freeTemplatesTab = document.querySelector('.nav-link[href="#free-templates"]');
948 if (freeTemplatesTab) freeTemplatesTab.classList.remove('active');
949 const freeTemplatesPane = document.querySelector('#free-templates');
950 if (freeTemplatesPane) freeTemplatesPane.classList.remove('active', 'show');
951
952 const dashboardTab = document.querySelector('.nav-link[href="#dashboard"]');
953 if (dashboardTab) dashboardTab.classList.remove('active');
954 const dashboardPane = document.querySelector('#dashboard');
955 if (dashboardPane) dashboardPane.classList.remove('active', 'show');
956
957 sessionStorage.removeItem('fleximp_auto_continue_pro');
958 const importData = sessionStorage.getItem('fleximp_import_data');
959 if (!importData) {
960 console.error('No import data found in sessionStorage');
961 return;
962 }
963
964 const {
965 textDomain,
966 jsonPath,
967 contentPath,
968 templateCss,
969 templateJs,
970 templatePhp,
971 plugins,
972 templateResponsiveImage,
973 title: templateTitle
974 } = JSON.parse(importData);
975
976 const importResponsiveImage = document.getElementById('import-process-responsive-image');
977 importResponsiveImage.src = templateResponsiveImage;
978
979 const importButton = document.createElement('button');
980 importButton.classList.add('flex-unique-import-button');
981 importButton.setAttribute('data-text-domain', textDomain);
982 importButton.setAttribute('data-json-path', jsonPath);
983 importButton.setAttribute('data-content-path', contentPath);
984 importButton.setAttribute('data-template-css', templateCss);
985 importButton.setAttribute('data-template-js', templateJs);
986 importButton.setAttribute('data-template-php', templatePhp);
987 importButton.setAttribute('data-plugins', plugins);
988 importButton.setAttribute('data-title', templateTitle);
989
990 document.body.appendChild(importButton);
991 runImportProcess(); // Continue import
992 }
993 });
994
995
996 /**
997 * Runs the asynchronous import process for templates, including plugin installation, inner pages, and demo content.
998 */
999 async function runImportProcess() {
1000 document.getElementById('import-process-confirm').style.display = 'none';
1001 document.getElementById('import-process-plugins').style.display = 'block';
1002 const importSection = document.querySelector('#templates .flex-templates-import-process');
1003 importSection.style.display = 'block';
1004 importSection.scrollIntoView({ behavior: 'smooth' });
1005
1006 const importBtn = document.querySelector('.flex-unique-import-button');
1007 const selectedPlugins = JSON.parse(importBtn.getAttribute('data-plugins'));
1008 const jsonPath = importBtn.getAttribute('data-json-path');
1009 const contentPath = importBtn.getAttribute('data-content-path');
1010 const templateCss = importBtn.getAttribute('data-template-css');
1011 const templateJs = importBtn.getAttribute('data-template-js');
1012 const textDomain = importBtn.getAttribute('data-text-domain');
1013 const templatePhp = importBtn.getAttribute('data-template-php');
1014
1015 // Step 1: Install Plugins
1016 updateStepStatus('step-plugins', 'In Process');
1017 const installPlugin = (plugin) => {
1018 return new Promise((resolve) => {
1019 jQuery.ajax({
1020 url: fleximp_ajax_object.ajax_url,
1021 method: 'POST',
1022 data: {
1023 action: 'fleximp_checked_install_plugin',
1024 plugin_slug: plugin.text_domain,
1025 plugin_name: plugin.name,
1026 plugin_main_file: plugin.main_file
1027 },
1028 success: function (response) {
1029 updateProgress(33, `Installed plugin: ${plugin.name}`);
1030 },
1031 error: function (xhr, status, error) {
1032 console.error(`Error installing plugin ${plugin.name}:`, error);
1033 },
1034 complete: function () {
1035 resolve();
1036 }
1037 });
1038 });
1039 };
1040
1041 const selectedPluginsObj = Object.values(selectedPlugins);
1042
1043 // Install plugins one by one
1044 for (const plugin of selectedPluginsObj) {
1045 await installPlugin(plugin);
1046 }
1047 updateStepStatus('step-plugins', 'Done');
1048
1049 // Step 2: Import Inner Pages
1050 updateStepStatus('step-inner-pages', 'In Process');
1051 await new Promise((resolve) => {
1052 jQuery.ajax({
1053 url: fleximp_ajax_object.ajax_url,
1054 method: 'POST',
1055 data: {
1056 action: 'fleximp_import_inner_pages_data',
1057 nonce: fleximp_ajax_object.fleximp_nonce,
1058 content_path: contentPath,
1059 text_domain: textDomain,
1060 template_css: templateCss,
1061 template_js: templateJs,
1062 template_php: templatePhp
1063 },
1064 success: function (response) {
1065 updateProgress(75, 'Importing inner pages success');
1066 updateStepStatus('step-inner-pages', 'Done');
1067 },
1068 error: function (xhr, status, error) {
1069 console.error('Error importing inner pages data:', error);
1070 updateStepStatus('step-inner-pages', 'Error');
1071 },
1072 complete: function () {
1073 resolve();
1074 }
1075 });
1076 });
1077
1078 // Step 3: Import Demo Content
1079 updateStepStatus('step-content', 'In Process');
1080 await new Promise((resolve) => {
1081 jQuery.ajax({
1082 url: fleximp_ajax_object.ajax_url,
1083 method: 'POST',
1084 data: {
1085 action: 'fleximp_import_demo_content',
1086 text_domain: textDomain,
1087 json_path: jsonPath
1088 },
1089 success: function (response) {
1090 updateProgress(100, 'Import success');
1091 updateStepStatus('step-content', 'Done');
1092 },
1093 error: function (xhr, status, error) {
1094 console.error('Error importing content:', error);
1095 updateStepStatus('step-content', 'Error');
1096 },
1097 complete: function () {
1098 resolve();
1099 }
1100 });
1101 });
1102
1103 const continueButton = document.getElementById('import-process-continue-btn');
1104 if (continueButton) continueButton.style.display = 'inline-block';
1105
1106 document.getElementById('import-process-plugins').style.display = 'none';
1107 document.getElementById('import-process-done').style.display = 'block';
1108
1109 setTimeout(() => location.reload(), 5000);
1110 }
1111
1112 /**
1113 * Updates the status icon and text for a given import step.
1114 * @param {string} stepId - The ID of the step element.
1115 * @param {string} status - The status to set ('In Process', 'Done', 'Error').
1116 */
1117 function updateStepStatus(stepId, status) {
1118 const statusElement = document.getElementById(`${stepId}-status`);
1119 if (statusElement) {
1120 const statusIcon = statusElement.querySelector('.status-icon');
1121 if (statusIcon) {
1122 if (status === 'In Process') {
1123 statusIcon.innerHTML = '<span class="loader"></span>';
1124 } else if (status === 'Done') {
1125 statusIcon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/></svg>';
1126 } else if (status === 'Error') {
1127 statusIcon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9L289 241l47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4 24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"/></svg>';
1128 } else {
1129 statusIcon.innerHTML = '';
1130 }
1131 }
1132 statusElement.innerHTML = statusElement.innerHTML.replace(/Not Started|In Process|Done|Error/g, status);
1133 }
1134 }