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 / free-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
free-template-admin.js
931 lines
1 /**
2 * Free Templates Tab — fetches and displays only free (free_pro === 'Free') templates.
3 * All element IDs are prefixed with "free-" to avoid conflicts with the main Templates tab.
4 */
5 document.addEventListener('DOMContentLoaded', function () {
6 let currentPage = 1;
7 let totalPages = 1;
8 let currentCategoryId = null;
9 let currentCategorySlug = null;
10 let currentSearchQuery = null;
11 const templatesApiEndpoint = fleximp_template_ajax_object.apiEndpoint;
12 let isFetchingTemplates = false;
13 let isFetchingCategories = false;
14
15 // -------------------------------------------------------------------------
16 // Loader helpers
17 // -------------------------------------------------------------------------
18 function showLoader(num = 6) {
19 const loader = document.getElementById('free-templates-skeleton-loader');
20 const list = document.getElementById('free-templates-list');
21 if (list) list.innerHTML = '';
22 if (loader) {
23 const grid = loader.querySelector('.skeleton-grid');
24 if (grid) {
25 grid.innerHTML = '';
26 for (let i = 0; i < num; i++) {
27 const item = document.createElement('div');
28 item.className = 'skeleton-item';
29 item.innerHTML = `
30 <div class="skeleton-image"></div>
31 <div class="skeleton-content">
32 <div class="skeleton-title"></div>
33 <div class="skeleton-buttons">
34 <div class="skeleton-button"></div>
35 <div class="skeleton-button"></div>
36 </div>
37 </div>`;
38 grid.appendChild(item);
39 }
40 }
41 loader.style.display = 'block';
42 }
43 }
44
45 function hideLoader() {
46 const loader = document.getElementById('free-templates-skeleton-loader');
47 if (loader) loader.style.display = 'none';
48 }
49
50 // -------------------------------------------------------------------------
51 // Template data helpers
52 // -------------------------------------------------------------------------
53 function getTemplateData(template) {
54 let pluginsData = template.metafields?.selected_plugins || {};
55 if (Array.isArray(pluginsData)) pluginsData = {};
56 return {
57 textDomain: template.template_domain,
58 jsonPath: template.metafields?.json_path || '',
59 contentPath: template.metafields?.content_path || '',
60 templateCss: template.metafields?.template_css_path || '',
61 templateJs: template.metafields?.template_js_path || '',
62 templatePhp: template.metafields?.template_php_path || '',
63 plugins: JSON.stringify(pluginsData),
64 templateMobileImage: template.metafields?.template_screenshot || '',
65 templateResponsiveImage: template.metafields?.template_responsive_image || '',
66 title: template.title || '',
67 };
68 }
69
70 function setButtonDataAttributes(button, template) {
71 const data = getTemplateData(template);
72 button.setAttribute('data-text-domain', data.textDomain);
73 button.setAttribute('data-json-path', data.jsonPath);
74 button.setAttribute('data-content-path', data.contentPath);
75 button.setAttribute('data-template-php', data.templatePhp);
76 button.setAttribute('data-template-css', data.templateCss);
77 button.setAttribute('data-template-js', data.templateJs);
78 button.setAttribute('data-plugins', data.plugins);
79 button.setAttribute('data-template-mobile-image', data.templateMobileImage);
80 button.setAttribute('data-template-responsive-image', data.templateResponsiveImage);
81 button.setAttribute('data-title', data.title);
82 }
83
84 // -------------------------------------------------------------------------
85 // Import process UI
86 // -------------------------------------------------------------------------
87 function ensureElementor() {
88 if (fleximp_template_ajax_object.isElementorActive === '1') {
89 return Promise.resolve();
90 }
91 return fetch(fleximp_template_ajax_object.ajax_url, {
92 method: 'POST',
93 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
94 body: new URLSearchParams({
95 action: 'fleximp_checked_install_plugin',
96 plugin_slug: 'elementor',
97 plugin_name: 'Elementor',
98 plugin_main_file: 'elementor.php',
99 }),
100 })
101 .then(r => r.json())
102 .then(resp => {
103 if (!resp.success) {
104 throw new Error(resp.data?.message || 'Failed to install Elementor.');
105 }
106 });
107 }
108
109 function handleConfirmClick() {
110 const textDomain = this.getAttribute('data-text-domain');
111 const jsonPath = this.getAttribute('data-json-path');
112 const contentPath = this.getAttribute('data-content-path');
113 const templateCss = this.getAttribute('data-template-css');
114 const templateJs = this.getAttribute('data-template-js');
115 const templatePhp = this.getAttribute('data-template-php');
116 const plugins = this.getAttribute('data-plugins');
117 const templateResponsiveImage = this.getAttribute('data-template-responsive-image');
118 const templateMobileImage = this.getAttribute('data-template-mobile-image');
119 const templateTitle = this.getAttribute('data-title');
120
121 sessionStorage.setItem('fleximp_import_data', JSON.stringify({
122 textDomain, jsonPath, contentPath, templateCss, templateJs,
123 templatePhp, plugins, templateResponsiveImage, templateMobileImage, title: templateTitle,
124 }));
125
126 if (!textDomain) { alert('Text domain not found.'); return; }
127
128 ensureElementor()
129 .then(() => fetch(fleximp_ajax_object.ajax_url, {
130 method: 'POST',
131 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
132 body: new URLSearchParams({
133 action: 'fleximp_set_text_domain',
134 text_domain: textDomain,
135 template_php: templatePhp,
136 template_mobile_image: templateMobileImage,
137 template_title: templateTitle,
138 })
139 }))
140 .then(res => res.json())
141 .then(response => {
142 if (response.success) {
143 sessionStorage.setItem('fleximp_auto_continue_free', '1');
144 location.reload();
145 } else {
146 alert('Failed to update text domain.');
147 }
148 })
149 .catch(error => {
150 console.error('Import error:', error);
151 alert('Error: ' + error.message);
152 });
153 }
154
155 function showImportProcess(template) {
156 const importSection = document.querySelector('.flex-free-import-process');
157 const importTitle = document.getElementById('free-import-process-title');
158 const importDescription = document.getElementById('free-import-process-description');
159 const importMobileTitle = document.getElementById('free-import-process-mobile-title');
160 const importMobileImage = document.getElementById('free-import-process-mobile-image');
161 const importScreenshot = document.getElementById('free-import-process-mid-image');
162 const importResponsive = document.getElementById('free-import-process-responsive-image');
163
164 if (importSection && importScreenshot && importTitle && importMobileTitle && importMobileImage && importResponsive) {
165 importTitle.textContent = template.title;
166 importDescription.textContent = template.metafields?.template_desc || '';
167 importMobileTitle.textContent = template.title;
168 importMobileImage.src = template.metafields?.template_screenshot || '';
169 importScreenshot.src = template.metafields?.template_screenshot || '';
170 importResponsive.src = template.metafields?.template_responsive_image || '';
171 importSection.style.display = 'block';
172 importSection.scrollIntoView({ behavior: 'smooth' });
173
174 const confirmBtn = document.getElementById('free-import-process-confirm-btn');
175 if (confirmBtn) {
176 setButtonDataAttributes(confirmBtn, template);
177 sessionStorage.setItem('fleximp_import_data', JSON.stringify(getTemplateData(template)));
178 confirmBtn.addEventListener('click', handleConfirmClick, { once: true });
179 }
180
181 const cancelBtn = document.getElementById('free-import-process-cancel-btn');
182 if (cancelBtn) {
183 cancelBtn.addEventListener('click', () => { importSection.style.display = 'none'; });
184 }
185
186 const demoUrl = template.metafields?.demo_url || '#';
187 const docUrl = template.metafields?.documentation_url || '';
188 const buyUrl = template.metafields?.buy_now_url || '#';
189
190 const upgradeBtn = document.getElementById('free-upgrade-pro-btn');
191 if (upgradeBtn) upgradeBtn.href = buyUrl;
192
193 const doneUpgradeBtn = document.getElementById('free-done-upgrade-pro-btn');
194 if (doneUpgradeBtn) doneUpgradeBtn.href = buyUrl;
195
196 const liveDemoBtn = document.getElementById('free-live-demo-btn');
197 if (liveDemoBtn) liveDemoBtn.href = demoUrl;
198
199 const liveDocBtn = document.getElementById('free-document-url-btn');
200 if (liveDocBtn) {
201 liveDocBtn.href = docUrl;
202 liveDocBtn.style.display = docUrl ? 'inline-block' : 'none';
203 }
204
205 hideLoader();
206 }
207 }
208
209 function showDoneProcess() {
210 const importSection = document.querySelector('.flex-free-import-process');
211 const uninstallBtn = document.getElementById('free-import-process-uninstall-btn');
212 if (importSection && uninstallBtn) {
213 importSection.style.display = 'block';
214 importSection.scrollIntoView({ behavior: 'smooth' });
215 document.getElementById('free-import-process-confirm').style.display = 'none';
216 document.getElementById('free-import-process-plugins').style.display = 'none';
217 document.getElementById('free-import-process-done').style.display = 'block';
218 uninstallBtn.style.display = 'inline-block';
219 hideLoader();
220 }
221 }
222
223 // -------------------------------------------------------------------------
224 // Fetch & display templates (free only)
225 // -------------------------------------------------------------------------
226 function fetchTemplates(categoryId = null, categorySlug = null, page = 1, searchQuery = null) {
227 if (isFetchingTemplates) return;
228 isFetchingTemplates = true;
229 showLoader();
230
231 if (categoryId && categorySlug) {
232 currentCategoryId = categoryId;
233 currentCategorySlug = categorySlug;
234 }
235 currentPage = page;
236 currentSearchQuery = searchQuery;
237
238 const themeAuthor = fleximp_template_ajax_object.active_theme_author?.toLowerCase() || '';
239 const isFlexTheme = themeAuthor === 'flextheme';
240 const currentTextDomain = fleximp_template_ajax_object.fleximp_current_text_domain;
241
242 const requestBody = {
243 action: 'getTemplates',
244 per_page: 6,
245 page: currentPage,
246 free_pro: 'Free',
247 };
248
249 requestBody.free_theme_domain = isFlexTheme ? currentTextDomain : '';
250
251 if (currentCategoryId && currentCategorySlug) {
252 requestBody.product_cat = currentCategoryId;
253 } else {
254 requestBody.product_cat = 15;
255 }
256
257 if (searchQuery) requestBody.search = searchQuery;
258
259 fetch(templatesApiEndpoint, {
260 method: 'POST',
261 headers: { 'Content-Type': 'application/json' },
262 body: JSON.stringify(requestBody),
263 })
264 .then(response => response.json())
265 .then(async data => {
266 if (data.success && data.data) {
267 let templates = data.data.posts || data.data.data?.posts;
268 const freeTemplate = data?.data?.data?.free_template || data?.data?.free_template || data?.free_template || null;
269
270 if (Array.isArray(templates)) {
271 // Keep only free templates as a safety client-side guard
272 templates = templates.filter(t => t.metafields?.free_pro !== 'Pro');
273
274 // If there's a free_template, show the import process for it
275 if (freeTemplate) {
276 showImportProcess(freeTemplate);
277 }
278
279 await displayTemplates(templates, freeTemplate);
280 totalPages = data.data.total_pages || (data.data.data ? data.data.data.total_pages : 1);
281 updatePaginationControls();
282 } else {
283 await displayTemplates([]);
284 }
285 } else {
286 await displayTemplates([]);
287 }
288 isFetchingTemplates = false;
289 })
290 .catch(async error => {
291 console.error('Free Templates Network Error:', error);
292 await displayTemplates([]);
293 isFetchingTemplates = false;
294 });
295 }
296
297 // -------------------------------------------------------------------------
298 // Pagination
299 // -------------------------------------------------------------------------
300 function updatePaginationControls() {
301 const container = document.getElementById('free-pagination-controls');
302 if (!container) return;
303 container.innerHTML = '';
304
305 const prevLi = document.createElement('li');
306 prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
307 prevLi.innerHTML = `<a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true"><i class="fa-solid fa-angle-left" style="color:hsl(0,0%,100%);"></i></span></a>`;
308 prevLi.addEventListener('click', e => {
309 e.preventDefault();
310 if (currentPage > 1) fetchTemplates(currentCategoryId, currentCategorySlug, currentPage - 1, currentSearchQuery);
311 });
312 container.appendChild(prevLi);
313
314 const maxVisible = 6;
315 let startPage = Math.max(1, currentPage - Math.floor(maxVisible / 2));
316 let endPage = Math.min(totalPages, startPage + maxVisible - 1);
317 if (endPage - startPage + 1 < maxVisible) startPage = Math.max(1, endPage - maxVisible + 1);
318
319 if (startPage > 1) {
320 const firstLi = document.createElement('li');
321 firstLi.className = 'page-item';
322 firstLi.innerHTML = `<a class="page-link" href="#">1</a>`;
323 firstLi.addEventListener('click', e => { e.preventDefault(); fetchTemplates(currentCategoryId, currentCategorySlug, 1, currentSearchQuery); });
324 container.appendChild(firstLi);
325 if (startPage > 2) {
326 const el = document.createElement('li');
327 el.className = 'page-item disabled';
328 el.innerHTML = `<span class="page-link">...</span>`;
329 container.appendChild(el);
330 }
331 }
332
333 for (let i = startPage; i <= endPage; i++) {
334 const li = document.createElement('li');
335 li.className = `page-item ${i === currentPage ? 'active' : ''}`;
336 li.innerHTML = `<a class="page-link" href="#">${i}</a>`;
337 li.addEventListener('click', (e => {
338 const pg = i;
339 return ev => { ev.preventDefault(); fetchTemplates(currentCategoryId, currentCategorySlug, pg, currentSearchQuery); };
340 })());
341 container.appendChild(li);
342 }
343
344 if (endPage < totalPages) {
345 if (endPage < totalPages - 1) {
346 const el = document.createElement('li');
347 el.className = 'page-item disabled';
348 el.innerHTML = `<span class="page-link">...</span>`;
349 container.appendChild(el);
350 }
351 const lastLi = document.createElement('li');
352 lastLi.className = 'page-item';
353 lastLi.innerHTML = `<a class="page-link" href="#">${totalPages}</a>`;
354 lastLi.addEventListener('click', e => { e.preventDefault(); fetchTemplates(currentCategoryId, currentCategorySlug, totalPages); });
355 container.appendChild(lastLi);
356 }
357
358 const nextLi = document.createElement('li');
359 nextLi.className = `page-item ${currentPage === totalPages ? 'disabled' : ''}`;
360 nextLi.innerHTML = `<a class="page-link" href="#" aria-label="Next"><span aria-hidden="true"><i class="fa-solid fa-angle-right" style="color:hsl(0,0%,100%);"></i></span></a>`;
361 nextLi.addEventListener('click', e => {
362 e.preventDefault();
363 if (currentPage < totalPages) fetchTemplates(currentCategoryId, currentCategorySlug, currentPage + 1);
364 });
365 container.appendChild(nextLi);
366 }
367
368 // -------------------------------------------------------------------------
369 // Fetch & populate categories
370 // -------------------------------------------------------------------------
371 function fetchCategories() {
372 if (isFetchingCategories) return;
373 isFetchingCategories = true;
374
375 fetch(templatesApiEndpoint, {
376 method: 'POST',
377 headers: { 'Content-Type': 'application/json' },
378 body: JSON.stringify({ action: 'getCategories' }),
379 })
380 .then(r => r.json())
381 .then(data => {
382 if (data.success && Array.isArray(data.data.data)) {
383 displayCategories(data.data.data);
384 }
385 })
386 .catch(err => console.error('Free Templates category error:', err))
387 .finally(() => { isFetchingCategories = false; });
388 }
389
390 function displayCategories(categories) {
391 const select = document.getElementById('free-categories-list');
392 if (!select) return;
393 select.innerHTML = '';
394 categories.forEach((cat, index) => {
395 const opt = document.createElement('option');
396 opt.value = cat.id;
397 opt.setAttribute('data-slug', cat.slug);
398 opt.textContent = cat.name;
399 if (index === 0) opt.selected = true;
400 select.appendChild(opt);
401 });
402 if (categories.length > 0) {
403 fetchTemplates(categories[0].id, categories[0].slug);
404 }
405 }
406
407 // -------------------------------------------------------------------------
408 // Display templates
409 // -------------------------------------------------------------------------
410 async function displayTemplates(templates, freeTemplate = null) {
411 const list = document.getElementById('free-templates-list');
412 if (!list) { hideLoader(); return; }
413 list.innerHTML = '';
414
415 const activeImportedTemplate = fleximp_template_ajax_object.imported_template;
416 const activeThemeTextDomain = fleximp_template_ajax_object.fleximp_current_text_domain;
417
418 const filtered = templates.filter(t =>
419 t.template_domain !== activeImportedTemplate &&
420 t.template_domain !== activeThemeTextDomain
421 );
422
423 if (!filtered.length) {
424 list.innerHTML = `<div class="no-templates-message">No free templates found</div>`;
425 hideLoader();
426 const isImportedFree = templates.some(t => t.template_domain === activeImportedTemplate) ||
427 (freeTemplate !== null && freeTemplate.template_domain === activeImportedTemplate);
428 if (activeImportedTemplate && isImportedFree && activeImportedTemplate === activeThemeTextDomain) {
429 showDoneProcess();
430 }
431 return;
432 }
433
434 filtered.forEach(template => {
435 const templateSlug = template.title.toLowerCase().replace(/ /g, '-');
436 const demoUrl = template.metafields?.demo_url || '#';
437 const selectedPlugins = template.metafields?.selected_plugins || [];
438 const jsonPath = template.metafields?.json_path || '';
439 const templateMainImage = template.metafields?.template_main_image || '';
440 const templateDesc = template.metafields?.template_desc || '';
441 const templateDocument = template.metafields?.documentation_url || '';
442 const templateMobileImage = template.metafields?.template_screenshot || '';
443 const templateResponsiveImage = template.metafields?.template_responsive_image || '';
444 const contentPath = template.metafields?.content_path || '';
445 const textDomain = template.template_domain || '';
446 const templateCss = template.metafields?.template_css_path || '';
447 const templateJs = template.metafields?.template_js_path || '';
448 const templatePhp = template.metafields?.template_php_path || '';
449 const buyNowUrl = template.metafields?.buy_now_url || '#';
450 const isImported = fleximp_template_ajax_object.imported_template;
451 const fleximpCurrentTextDomain = fleximp_template_ajax_object.fleximp_dynamic_text_domain;
452
453 let buttonHTML = '';
454
455 if (isImported === textDomain) {
456 buttonHTML = `
457 <button class="btn flex-template-buttons uninstall-btn"
458 data-template="${templateSlug}"
459 data-title="${template.title}"
460 data-plugins='${JSON.stringify(selectedPlugins)}'
461 data-json-path="${jsonPath}"
462 data-template-main-image="${templateMainImage}"
463 data-template-mobile-image="${templateMobileImage}"
464 data-template-responsive-image="${templateResponsiveImage}"
465 data-template-description="${templateDesc}"
466 data-template-doc-url="${templateDocument}"
467 data-content-path="${contentPath}"
468 data-text-domain="${textDomain}"
469 data-template-css="${templateCss}"
470 data-template-js="${templateJs}"
471 data-template-php="${templatePhp}">
472 Uninstall
473 </button>`;
474 } else {
475 if (activeThemeTextDomain !== textDomain) {
476 const installedSlugs = fleximp_template_ajax_object.installed_theme_slugs || [];
477 const isInstalled = installedSlugs.includes(textDomain);
478 const themeLabel = isInstalled ? 'ACTIVATE THEME' : 'INSTALL THEME';
479 buttonHTML = `
480 <button class="btn flex-unique-free-activate-theme-btn flex-template-buttons"
481 data-theme-slug="${textDomain}">
482 ${themeLabel}
483 </button>`;
484 } else {
485 buttonHTML = `
486 <button class="btn flex-unique-free-import-button flex-template-buttons import-btn"
487 data-template="${templateSlug}"
488 data-title="${template.title}"
489 data-plugins='${JSON.stringify(selectedPlugins)}'
490 data-json-path="${jsonPath}"
491 data-template-main-image="${templateMainImage}"
492 data-template-mobile-image="${templateMobileImage}"
493 data-template-responsive-image="${templateResponsiveImage}"
494 data-template-description="${templateDesc}"
495 data-template-doc-url="${templateDocument}"
496 data-content-path="${contentPath}"
497 data-text-domain="${textDomain}"
498 data-template-css="${templateCss}"
499 data-template-js="${templateJs}"
500 data-template-php="${templatePhp}"
501 data-buy-url="${buyNowUrl}"
502 data-demo-url="${demoUrl}">
503 IMPORT DEMO
504 </button>`;
505 }
506 }
507
508 list.innerHTML += `
509 <div class="template-item grid-item" style="position: relative;">
510 <div class="flex-imp-temp-inner-box"><img src="${templateMainImage}" alt="${template.title}" style=""></div>
511 <div class="fleximp-temp-buttons widgets-btn">
512 <div class="buttons widgets-btn pb-3">
513 <div class="btn-title">
514 <p class="template-title">${template.title}</p>
515 </div>
516 <div class="premium-badge">Free</div>
517 </div>
518 <div class="d-md-flex d-sm-block d-flex flex-gap-div">
519 <a href="${demoUrl}" target="_blank" class="btn flex-template-buttons">LIVE DEMO</a>
520 ${buttonHTML}
521 </div>
522 </div>
523 </div>`;
524 });
525
526 hideLoader();
527
528 const isImportedFree = templates.some(t => t.template_domain === activeImportedTemplate) ||
529 (freeTemplate !== null && freeTemplate.template_domain === activeImportedTemplate);
530 if (activeImportedTemplate && isImportedFree && activeImportedTemplate === activeThemeTextDomain) {
531 showDoneProcess();
532 }
533 }
534
535 // -------------------------------------------------------------------------
536 // Import button click (delegated)
537 // -------------------------------------------------------------------------
538 document.addEventListener('click', function (event) {
539 if (!event.target.classList.contains('flex-unique-free-import-button')) return;
540
541 const btn = event.target;
542 const templateTitle = btn.getAttribute('data-title');
543 const templateDesc = btn.getAttribute('data-template-description');
544 const templateMobileImage = btn.getAttribute('data-template-mobile-image');
545 const textDomain = btn.getAttribute('data-text-domain');
546 const jsonPath = btn.getAttribute('data-json-path');
547 const contentPath = btn.getAttribute('data-content-path');
548 const templateCss = btn.getAttribute('data-template-css');
549 const templateJs = btn.getAttribute('data-template-js');
550 const templatePhp = btn.getAttribute('data-template-php');
551 const plugins = btn.getAttribute('data-plugins');
552 const templateResponsiveImage = btn.getAttribute('data-template-responsive-image');
553 const demoUrl = btn.getAttribute('data-demo-url');
554 const docUrl = btn.getAttribute('data-template-doc-url') || '';
555
556 const importSection = document.querySelector('.flex-free-import-process');
557 const importTitle = document.getElementById('free-import-process-title');
558 const importDescription = document.getElementById('free-import-process-description');
559 const importMobileTitle = document.getElementById('free-import-process-mobile-title');
560 const importMobileImage = document.getElementById('free-import-process-mobile-image');
561 const importResponsive = document.getElementById('free-import-process-responsive-image');
562
563 if (importSection && importTitle && importMobileTitle && importMobileImage && importResponsive) {
564 importTitle.textContent = templateTitle;
565 importDescription.textContent = templateDesc;
566 importMobileTitle.textContent = templateTitle;
567 importMobileImage.src = templateMobileImage;
568 importResponsive.src = templateResponsiveImage;
569 importSection.style.display = 'block';
570 importSection.scrollIntoView({ behavior: 'smooth' });
571
572 const liveDemoBtn = document.getElementById('free-live-demo-btn');
573 if (liveDemoBtn) liveDemoBtn.href = demoUrl;
574
575 const upgradeProBtn = document.getElementById('free-upgrade-pro-btn');
576 if (upgradeProBtn) upgradeProBtn.href = btn.getAttribute('data-buy-url') || '#';
577
578 const liveDocBtn = document.getElementById('free-document-url-btn');
579 if (liveDocBtn) {
580 liveDocBtn.href = docUrl;
581 liveDocBtn.style.display = docUrl ? 'inline-block' : 'none';
582 }
583
584 const confirmBtn = document.getElementById('free-import-process-confirm-btn');
585 if (confirmBtn) {
586 confirmBtn.addEventListener('click', function () {
587 sessionStorage.setItem('fleximp_import_data', JSON.stringify({
588 textDomain, jsonPath, contentPath, templateCss, templateJs,
589 templatePhp, plugins, templateResponsiveImage, templateMobileImage,
590 title: templateTitle,
591 }));
592
593 if (!textDomain) { alert('Text domain not found.'); return; }
594
595 fetch(fleximp_ajax_object.ajax_url, {
596 method: 'POST',
597 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
598 body: new URLSearchParams({
599 action: 'fleximp_set_text_domain',
600 text_domain: textDomain,
601 template_php: templatePhp,
602 template_mobile_image: templateMobileImage,
603 template_title: templateTitle,
604 })
605 })
606 .then(r => r.json())
607 .then(resp => {
608 if (resp.success) {
609 sessionStorage.setItem('fleximp_auto_continue_free', '1');
610 location.reload();
611 } else {
612 alert('Failed to update text domain.');
613 }
614 })
615 .catch(err => {
616 console.error('Text domain AJAX error:', err);
617 alert('Error updating text domain.');
618 });
619 }, { once: true });
620 }
621
622 const cancelBtn = document.getElementById('free-import-process-cancel-btn');
623 if (cancelBtn) {
624 cancelBtn.addEventListener('click', () => { importSection.style.display = 'none'; }, { once: true });
625 }
626 }
627 });
628
629 // -------------------------------------------------------------------------
630 // Activate-theme button click (delegated)
631 // -------------------------------------------------------------------------
632 document.addEventListener('click', function (event) {
633 if (!event.target.classList.contains('flex-unique-free-activate-theme-btn')) return;
634
635 const btn = event.target;
636 const themeSlug = btn.getAttribute('data-theme-slug');
637 if (!themeSlug) return;
638
639 const originalLabel = btn.textContent.trim();
640 btn.disabled = true;
641 btn.textContent = originalLabel === 'ACTIVATE THEME' ? 'Activating...' : 'Installing...';
642
643 fetch(fleximp_template_ajax_object.ajax_url, {
644 method: 'POST',
645 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
646 body: new URLSearchParams({
647 action: 'fleximp_install_activate_theme',
648 security: fleximp_template_ajax_object.fleximp_nonce,
649 theme_slug: themeSlug,
650 }),
651 })
652 .then(r => r.json())
653 .then(resp => {
654 if (resp.success) {
655 btn.textContent = 'Activated! Reloading...';
656 window.location.reload();
657 } else {
658 const msg = resp.data?.message || 'Theme installation failed.';
659 alert(msg);
660 btn.disabled = false;
661 btn.textContent = originalLabel;
662 }
663 })
664 .catch(err => {
665 console.error('Theme install error:', err);
666 alert('An error occurred while installing the theme.');
667 btn.disabled = false;
668 btn.textContent = originalLabel;
669 });
670 });
671
672 // -------------------------------------------------------------------------
673 // Initialise — only fetch when the Free Templates tab becomes active
674 // -------------------------------------------------------------------------
675 let initialised = false;
676
677 function initFreeTemplates() {
678 if (initialised) return;
679 initialised = true;
680 showLoader();
681 fetchCategories();
682 }
683
684 // Check if Free Templates tab is already active on page load
685 if (jQuery('#free-templates').hasClass('active') || jQuery('#free-templates').hasClass('show')) {
686 initFreeTemplates();
687 }
688
689 // Bootstrap 3/4 tab shown event
690 jQuery(document).on('shown.bs.tab', 'a[href="#free-templates"]', function () {
691 initFreeTemplates();
692 });
693
694 // Category select
695 jQuery(document).on('change', '#free-categories-list', function () {
696 const selected = jQuery(this).find('option:selected');
697 fetchTemplates(selected.val(), selected.attr('data-slug'), 1, currentSearchQuery);
698 });
699
700 // Search
701 jQuery(document).on('input', '#free-flex-templates-search', debounce(function () {
702 const selected = jQuery('#free-categories-list option:selected');
703 fetchTemplates(selected.val(), selected.attr('data-slug'), 1, jQuery(this).val());
704 }, 300));
705 });
706
707 /**
708 * Updates the free-templates progress bar and label during the import process.
709 * @param {number} percent - Progress percentage (0–100).
710 * @param {string} message - Status message to display alongside the percentage.
711 */
712 function updateFreeProgress(percent, message) {
713 const bar = document.getElementById('free-import-process-progress-bar');
714 const text = document.getElementById('free-import-process-progress-text');
715 if (bar && text) {
716 bar.style.width = `${percent}%`;
717 text.textContent = `${message} (${percent}%)`;
718 }
719 }
720
721 /**
722 * Debounces a function to limit how often it can be called.
723 * @param {Function} func - The function to debounce.
724 * @param {number} delay - The delay in milliseconds.
725 * @returns {Function} The debounced function.
726 */
727 function debounce(func, delay) {
728 let timeoutId;
729 return function (...args) {
730 clearTimeout(timeoutId);
731 timeoutId = setTimeout(() => func.apply(this, args), delay);
732 };
733 }
734
735 /**
736 * Handles auto-continue import process after page reload for free templates.
737 */
738 window.addEventListener('DOMContentLoaded', function () {
739 if (sessionStorage.getItem('fleximp_auto_continue_free') === '1') {
740 // Check if this is a free template import
741 const importData = sessionStorage.getItem('fleximp_import_data');
742 if (!importData) return;
743
744 // Activate free-templates tab
745 const freeTemplatesTab = document.querySelector('.nav-link[href="#free-templates"]');
746 if (freeTemplatesTab) freeTemplatesTab.classList.add('active');
747 const freeTemplatesPane = document.querySelector('#free-templates');
748 if (freeTemplatesPane) freeTemplatesPane.classList.add('active', 'show');
749
750 // Deactivate other tabs
751 const templatesTab = document.querySelector('.nav-link[href="#templates"]');
752 if (templatesTab) templatesTab.classList.remove('active');
753 const templatesPane = document.querySelector('#templates');
754 if (templatesPane) templatesPane.classList.remove('active', 'show');
755
756 const dashboardTab = document.querySelector('.nav-link[href="#dashboard"]');
757 if (dashboardTab) dashboardTab.classList.remove('active');
758 const dashboardPane = document.querySelector('#dashboard');
759 if (dashboardPane) dashboardPane.classList.remove('active', 'show');
760
761 sessionStorage.removeItem('fleximp_auto_continue_free');
762
763 const {
764 textDomain,
765 jsonPath,
766 contentPath,
767 templateCss,
768 templateJs,
769 templatePhp,
770 plugins,
771 templateResponsiveImage,
772 title: templateTitle
773 } = JSON.parse(importData);
774
775 const importResponsiveImage = document.getElementById('free-import-process-responsive-image');
776 if (importResponsiveImage) importResponsiveImage.src = templateResponsiveImage;
777
778 const importButton = document.createElement('button');
779 importButton.classList.add('flex-unique-free-import-button');
780 importButton.setAttribute('data-text-domain', textDomain);
781 importButton.setAttribute('data-json-path', jsonPath);
782 importButton.setAttribute('data-content-path', contentPath);
783 importButton.setAttribute('data-template-css', templateCss);
784 importButton.setAttribute('data-template-js', templateJs);
785 importButton.setAttribute('data-template-php', templatePhp);
786 importButton.setAttribute('data-plugins', plugins);
787 importButton.setAttribute('data-title', templateTitle);
788
789 document.body.appendChild(importButton);
790 runFreeImportProcess(); // Continue import
791 }
792 });
793
794 /**
795 * Runs the asynchronous import process for free templates.
796 */
797 async function runFreeImportProcess() {
798 document.getElementById('free-import-process-confirm').style.display = 'none';
799 document.getElementById('free-import-process-plugins').style.display = 'block';
800 const importSection = document.querySelector('.flex-free-import-process');
801 importSection.style.display = 'block';
802 importSection.scrollIntoView({ behavior: 'smooth' });
803
804 const importBtn = document.querySelector('.flex-unique-free-import-button');
805 const selectedPlugins = JSON.parse(importBtn.getAttribute('data-plugins'));
806 const jsonPath = importBtn.getAttribute('data-json-path');
807 const contentPath = importBtn.getAttribute('data-content-path');
808 const templateCss = importBtn.getAttribute('data-template-css');
809 const templateJs = importBtn.getAttribute('data-template-js');
810 const textDomain = importBtn.getAttribute('data-text-domain');
811 const templatePhp = importBtn.getAttribute('data-template-php');
812
813 // Step 1: Install Plugins
814 updateFreeStepStatus('free-step-plugins', 'In Process');
815 const installPlugin = (plugin) => {
816 return new Promise((resolve) => {
817 jQuery.ajax({
818 url: fleximp_ajax_object.ajax_url,
819 method: 'POST',
820 data: {
821 action: 'fleximp_checked_install_plugin',
822 plugin_slug: plugin.text_domain,
823 plugin_name: plugin.name,
824 plugin_main_file: plugin.main_file
825 },
826 success: function (response) {
827 updateFreeProgress(33, `Installed plugin: ${plugin.name}`);
828 },
829 error: function (xhr, status, error) {
830 console.error(`Error installing plugin ${plugin.name}:`, error);
831 },
832 complete: function () {
833 resolve();
834 }
835 });
836 });
837 };
838
839 const selectedPluginsObj = Object.values(selectedPlugins);
840 for (const plugin of selectedPluginsObj) {
841 await installPlugin(plugin);
842 }
843 updateFreeStepStatus('free-step-plugins', 'Done');
844
845 // Step 2: Import Inner Pages
846 updateFreeStepStatus('free-step-inner-pages', 'In Process');
847 await new Promise((resolve) => {
848 jQuery.ajax({
849 url: fleximp_ajax_object.ajax_url,
850 method: 'POST',
851 data: {
852 action: 'fleximp_import_inner_pages_data',
853 nonce: fleximp_ajax_object.fleximp_nonce,
854 content_path: contentPath,
855 text_domain: textDomain,
856 template_css: templateCss,
857 template_js: templateJs,
858 template_php: templatePhp
859 },
860 success: function (response) {
861 updateFreeProgress(75, 'Importing inner pages success');
862 updateFreeStepStatus('free-step-inner-pages', 'Done');
863 },
864 error: function (xhr, status, error) {
865 console.error('Error importing inner pages data:', error);
866 updateFreeStepStatus('free-step-inner-pages', 'Error');
867 },
868 complete: function () {
869 resolve();
870 }
871 });
872 });
873
874 // Step 3: Import Demo Content
875 updateFreeStepStatus('free-step-content', 'In Process');
876 await new Promise((resolve) => {
877 jQuery.ajax({
878 url: fleximp_ajax_object.ajax_url,
879 method: 'POST',
880 data: {
881 action: 'fleximp_import_demo_content',
882 text_domain: textDomain,
883 json_path: jsonPath
884 },
885 success: function (response) {
886 updateFreeProgress(100, 'Import success');
887 updateFreeStepStatus('free-step-content', 'Done');
888 },
889 error: function (xhr, status, error) {
890 console.error('Error importing content:', error);
891 updateFreeStepStatus('free-step-content', 'Error');
892 },
893 complete: function () {
894 resolve();
895 }
896 });
897 });
898
899 const continueButton = document.getElementById('free-import-process-continue-btn');
900 if (continueButton) continueButton.style.display = 'inline-block';
901
902 document.getElementById('free-import-process-plugins').style.display = 'none';
903 document.getElementById('free-import-process-done').style.display = 'block';
904
905 setTimeout(() => location.reload(), 5000);
906 }
907
908 /**
909 * Updates the status icon and text for a given import step.
910 * @param {string} stepId - The ID of the step element.
911 * @param {string} status - The status to set ('In Process', 'Done', 'Error').
912 */
913 function updateFreeStepStatus(stepId, status) {
914 const statusElement = document.getElementById(`${stepId}-status`);
915 if (statusElement) {
916 const statusIcon = statusElement.querySelector('.status-icon');
917 if (statusIcon) {
918 if (status === 'In Process') {
919 statusIcon.innerHTML = '<span class="loader"></span>';
920 } else if (status === 'Done') {
921 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>';
922 } else if (status === 'Error') {
923 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>';
924 } else {
925 statusIcon.innerHTML = '';
926 }
927 }
928 statusElement.innerHTML = statusElement.innerHTML.replace(/Not Started|In Process|Done|Error/g, status);
929 }
930 }
931