/** * IGNY8 UNIFIED JAVASCRIPT - PRODUCTION READY * ============================================ * * This file contains ALL JavaScript functionality for the Igny8 plugin. * Cleaned and optimized for production use. * * @package Igny8Compact * @since 1.0.0 */ /* ========================================= Planner Settings - Sector Selection ========================================= */ window.initializePlannerSettings = function() { // Only initialize if we're on the planner home page if (!window.IGNY8_PAGE || window.IGNY8_PAGE.module !== 'planner' || window.IGNY8_PAGE.submodule !== 'home') { return; } const parentSelect = document.getElementById('igny8-parent-sector'); const lockButton = document.getElementById('igny8-lock-parent'); const childSelection = document.getElementById('igny8-child-selection'); const childCheckboxes = document.getElementById('igny8-child-checkboxes'); const saveButton = document.getElementById('igny8-save-selection'); const finalSelection = document.getElementById('igny8-final-selection'); const selectedDisplay = document.getElementById('igny8-selected-sectors-display'); const editButton = document.getElementById('igny8-edit-selection'); if (!parentSelect || !lockButton || !childSelection || !childCheckboxes || !saveButton || !finalSelection || !selectedDisplay || !editButton) { return; } let selectedParent = null; let selectedChildren = []; // Load parent sectors loadParentSectors(); // Load saved selection if exists loadSavedSelection(); // Sample data creation button const createSampleButton = document.getElementById('igny8-create-sample-sectors'); if (createSampleButton) { createSampleButton.addEventListener('click', createSampleSectors); } // Parent sector change handler parentSelect.addEventListener('change', function() { if (this.value) { lockButton.style.display = 'inline-block'; } else { lockButton.style.display = 'none'; } }); // Lock parent selection lockButton.addEventListener('click', function() { selectedParent = parentSelect.value; if (selectedParent) { parentSelect.disabled = true; this.style.display = 'none'; loadChildSectors(selectedParent); childSelection.style.display = 'block'; } }); // Save selection saveButton.addEventListener('click', function() { const checkedBoxes = childCheckboxes.querySelectorAll('input[type="checkbox"]:checked'); selectedChildren = Array.from(checkedBoxes).map(cb => { const label = document.querySelector(`label[for="${cb.id}"]`); return { id: cb.value, name: label ? label.textContent.trim() : cb.value }; }); console.log('Selected children:', selectedChildren); // Debug log if (selectedChildren.length === 0) { alert('Please select at least one child sector.'); return; } saveSectorSelection(selectedParent, selectedChildren); }); // Edit selection editButton.addEventListener('click', function() { resetSelection(); }); function loadParentSectors() { const formData = new FormData(); formData.append('action', 'igny8_get_parent_sectors'); formData.append('nonce', window.IGNY8_PAGE.nonce); fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { parentSelect.innerHTML = ''; data.data.forEach(sector => { const option = document.createElement('option'); option.value = sector.id; option.textContent = sector.name; parentSelect.appendChild(option); }); } }) .catch(error => console.error('Error loading parent sectors:', error)); } function loadChildSectors(parentId) { const formData = new FormData(); formData.append('action', 'igny8_get_child_sectors'); formData.append('nonce', window.IGNY8_PAGE.nonce); formData.append('parent_id', parentId); fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { childCheckboxes.innerHTML = ''; data.data.forEach(sector => { const checkboxContainer = document.createElement('div'); checkboxContainer.className = 'igny8-checkbox-item'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = 'child-sector-' + sector.id; checkbox.value = sector.id; const label = document.createElement('label'); label.htmlFor = 'child-sector-' + sector.id; label.textContent = sector.name; checkboxContainer.appendChild(checkbox); checkboxContainer.appendChild(label); childCheckboxes.appendChild(checkboxContainer); }); } }) .catch(error => console.error('Error loading child sectors:', error)); } function saveSectorSelection(parentId, children) { console.log('Saving sector selection:', { parentId, children }); // Debug log const formData = new FormData(); formData.append('action', 'igny8_save_sector_selection'); formData.append('nonce', window.IGNY8_PAGE.nonce); formData.append('parent_id', parentId); formData.append('children_count', children.length); // Add each child as separate form fields children.forEach((child, index) => { formData.append(`child_${index}_id`, child.id); formData.append(`child_${index}_name`, child.name); }); fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { displayFinalSelection(data.data.parent, data.data.children); } else { let errorMessage = 'Unknown error'; if (data.data) { if (typeof data.data === 'string') { errorMessage = data.data; } else if (data.data.message) { errorMessage = data.data.message; } else { errorMessage = JSON.stringify(data.data); } } alert('Error saving selection: ' + errorMessage); } }) .catch(error => { console.error('Error saving selection:', error); alert('Error saving selection. Please try again.'); }); } function loadSavedSelection() { const formData = new FormData(); formData.append('action', 'igny8_get_saved_sector_selection'); formData.append('nonce', window.IGNY8_PAGE.nonce); fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success && data.data) { displayFinalSelection(data.data.parent, data.data.children); } }) .catch(error => console.error('Error loading saved selection:', error)); } function displayFinalSelection(parent, children) { selectedParent = parent.id; selectedChildren = children; // Hide all selection steps document.getElementById('igny8-parent-selection').style.display = 'none'; childSelection.style.display = 'none'; // Show final selection selectedDisplay.innerHTML = `
Parent Sector: ${parent.name}
Child Sectors:
`; finalSelection.style.display = 'block'; } function resetSelection() { selectedParent = null; selectedChildren = []; // Show parent selection, hide others document.getElementById('igny8-parent-selection').style.display = 'block'; childSelection.style.display = 'none'; finalSelection.style.display = 'none'; // Reset form parentSelect.disabled = false; parentSelect.value = ''; lockButton.style.display = 'none'; childCheckboxes.innerHTML = ''; } function createSampleSectors() { const button = document.getElementById('igny8-create-sample-sectors'); if (!button) return; const originalText = button.textContent; button.textContent = 'Creating...'; button.disabled = true; const formData = new FormData(); formData.append('action', 'igny8_create_sample_sectors'); formData.append('nonce', window.IGNY8_PAGE.nonce); fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { alert('Sample sectors created successfully! Refreshing the page...'); location.reload(); } else { alert('Error: ' + (data.data || 'Unknown error')); } }) .catch(error => { console.error('Error creating sample sectors:', error); alert('Error creating sample sectors. Please try again.'); }) .finally(() => { button.textContent = originalText; button.disabled = false; }); } }; /* ========================================= AI Integration Form ========================================= */ window.initializeAIIntegrationForm = function() { // Only initialize if we're on the planner home page if (!window.IGNY8_PAGE || window.IGNY8_PAGE.module !== 'planner' || window.IGNY8_PAGE.submodule !== 'home') { return; } const form = document.getElementById('igny8-ai-integration-form'); if (!form) { return; } // Handle mode change to show/hide AI features and prompts section const modeRadios = form.querySelectorAll('input[name="igny8_planner_mode"]'); const aiFeatures = document.getElementById('igny8-ai-features'); const promptsSection = document.querySelector('.igny8-planner-prompts'); modeRadios.forEach(radio => { radio.addEventListener('change', function() { if (this.value === 'ai') { aiFeatures.style.display = ''; if (promptsSection) { promptsSection.style.display = ''; } } else { aiFeatures.style.display = 'none'; if (promptsSection) { promptsSection.style.display = 'none'; } } }); }); form.addEventListener('submit', function(e) { e.preventDefault(); const formData = new FormData(form); // Determine the correct action based on the current page const currentPage = window.location.href; if (currentPage.includes('writer')) { formData.append('action', 'igny8_save_writer_ai_settings'); formData.append('nonce', window.IGNY8_PAGE.nonce); } else { formData.append('action', 'igny8_save_ai_integration_settings'); formData.append('nonce', window.IGNY8_PAGE.nonce); } // Show loading state const submitBtn = form.querySelector('button[type="submit"]'); const originalText = submitBtn.textContent; submitBtn.textContent = 'Saving...'; submitBtn.disabled = true; fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { // Show success message igny8GlobalNotification('AI Integration settings saved successfully!', 'success'); // Reload page to update AI buttons setTimeout(() => { window.location.reload(); }, 1500); } else { // Show error message const errorMsg = data.data?.message || 'Error saving settings'; igny8GlobalNotification(errorMsg, 'error'); } }) .catch(error => { console.error('Error saving AI integration settings:', error); igny8GlobalNotification('Error saving settings. Please try again.', 'error'); }) .finally(() => { // Reset button state submitBtn.textContent = originalText; submitBtn.disabled = false; }); }); } // Initialize AI action buttons for tables window.initializeAIActionButtons = function() { // Only initialize if we're on a planner or writer submodule page if (!window.IGNY8_PAGE || !window.IGNY8_PAGE.submodule) { return; } // Only initialize for planner and writer modules if (window.IGNY8_PAGE.module !== 'planner' && window.IGNY8_PAGE.module !== 'writer') { return; } const tableId = window.IGNY8_PAGE.tableId; if (!tableId) return; // AI Clustering button const clusterBtn = document.getElementById(`${tableId}_ai_cluster_btn`); if (clusterBtn) { clusterBtn.addEventListener('click', function() { const selectedIds = getSelectedRowIds(tableId); if (selectedIds.length === 0) { igny8GlobalNotification('Please select keywords to cluster', 'error'); return; } if (selectedIds.length > 20) { igny8GlobalNotification('Maximum 20 keywords allowed for clustering', 'error'); return; } // Check if sector is selected before clustering checkSectorSelectionBeforeClustering(selectedIds); }); } // AI Ideas button const ideasBtn = document.getElementById(`${tableId}_ai_ideas_btn`); if (ideasBtn) { ideasBtn.addEventListener('click', function() { const selectedIds = getSelectedRowIds(tableId); if (selectedIds.length === 0) { igny8GlobalNotification('Please select clusters to generate ideas', 'error'); return; } if (selectedIds.length > 5) { igny8GlobalNotification('Maximum 5 clusters allowed for idea generation', 'error'); return; } processAIIdeas(selectedIds); }); } // AI Generate Images button const generateImagesBtn = document.getElementById(`${tableId}_generate_images_btn`); if (generateImagesBtn) { console.log('Igny8: Generate Images button found for table:', tableId); generateImagesBtn.addEventListener('click', function(e) { console.log('Igny8: Generate Images button clicked'); e.preventDefault(); e.stopPropagation(); const selectedIds = getSelectedRowIds(tableId); console.log('Igny8: Selected IDs:', selectedIds); console.log('Igny8: Post IDs being sent for image generation:', selectedIds); if (selectedIds.length === 0) { igny8GlobalNotification('Please select posts to generate images', 'error'); return; } if (selectedIds.length > 10) { igny8GlobalNotification('Maximum 10 posts allowed for image generation', 'error'); return; } // Only for drafts table console.log('Igny8: Calling processAIImageGenerationDrafts with', selectedIds.length, 'posts'); processAIImageGenerationDrafts(selectedIds); }); } else { console.log('Igny8: Generate Images button NOT found for table:', tableId); } // Queue to Writer button is now handled by the bulk selection system // No need for separate event listener as it's integrated into updateStates() } // Get selected row IDs from table function getSelectedRowIds(tableId) { const checkboxes = document.querySelectorAll(`#table-${tableId}-body input[type="checkbox"]:checked`); const ids = []; console.log(`Igny8: Found ${checkboxes.length} checked checkboxes for table ${tableId}`); checkboxes.forEach((checkbox, index) => { const row = checkbox.closest('tr'); const rowId = row.getAttribute('data-id'); console.log(`Igny8: Checkbox ${index + 1} - Row ID: ${rowId}`); if (rowId && !isNaN(rowId)) { ids.push(parseInt(rowId)); console.log(`Igny8: Added Post ID ${rowId} to selection`); } }); console.log(`Igny8: Final selected Post IDs: ${ids.join(', ')}`); return ids; } // Process AI Clustering function processAIClustering(keywordIds) { const formData = new FormData(); formData.append('action', 'igny8_ai_cluster_keywords'); formData.append('nonce', window.IGNY8_PAGE.nonce); formData.append('keyword_ids', JSON.stringify(keywordIds)); // Show progress modal showProgressModal('Auto Clustering', keywordIds.length); // Log client-side event start console.log('Igny8 AI: Starting clustering process for', keywordIds.length, 'keywords'); fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { if (data.success) { console.log('Igny8 AI: Clustering completed successfully', data.data); // Show success modal showSuccessModal('Auto Clustering Complete', data.data.clusters_created || keywordIds.length, data.data.message); // Reload table data try { if (window.loadTableData && window.IGNY8_PAGE?.tableId) { window.loadTableData(window.IGNY8_PAGE.tableId); } } catch (error) { console.error('Error reloading table data:', error); // Don't show error to user since clustering was successful } } else { console.error('Igny8 AI: Clustering failed', data.data); // Close progress modal and show error if (currentProgressModal) { currentProgressModal.remove(); currentProgressModal = null; } igny8GlobalNotification(data.data?.message || 'AI clustering failed', 'error'); } }) .catch(error => { console.error('Error processing AI clustering:', error); // Close progress modal and show error if (currentProgressModal) { currentProgressModal.remove(); currentProgressModal = null; } igny8GlobalNotification('Error processing AI clustering', 'error'); }); } // Process AI Ideas Generation function processAIIdeas(clusterIds) { const formData = new FormData(); formData.append('action', 'igny8_ai_generate_ideas'); formData.append('nonce', window.IGNY8_PAGE.nonce); formData.append('cluster_ids', JSON.stringify(clusterIds)); // Show progress modal showProgressModal('Generate Ideas', clusterIds.length); // Log client-side event start console.log('Igny8 AI: Starting ideas generation process for', clusterIds.length, 'clusters'); fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { console.log('Igny8 AI: Ideas generation completed successfully', data.data); // Show success modal showSuccessModal('Ideas Generated', data.data.ideas_created || clusterIds.length, data.data.message); // Reload table data try { if (window.loadTableData && window.IGNY8_PAGE?.tableId) { window.loadTableData(window.IGNY8_PAGE.tableId); } } catch (error) { console.error('Error reloading table data:', error); // Don't show error to user since ideas generation was successful } } else { console.error('Igny8 AI: Ideas generation failed', data.data); // Close progress modal and show error if (currentProgressModal) { currentProgressModal.remove(); currentProgressModal = null; } igny8GlobalNotification(data.data?.message || 'AI ideas generation failed', 'error'); } }) .catch(error => { console.error('Error processing AI ideas:', error); // Close progress modal and show error if (currentProgressModal) { currentProgressModal.remove(); currentProgressModal = null; } igny8GlobalNotification('Error processing AI ideas', 'error'); }); } // Process AI Image Generation for Drafts (Sequential Image Processing) function processAIImageGenerationDrafts(postIds) { console.log('Igny8: processAIImageGenerationDrafts called with postIds:', postIds); // Get image generation settings from saved options (passed via wp_localize_script) const desktopEnabled = window.IGNY8_PAGE?.imageSettings?.desktop_enabled || false; const mobileEnabled = window.IGNY8_PAGE?.imageSettings?.mobile_enabled || false; const maxInArticleImages = window.IGNY8_PAGE?.imageSettings?.max_in_article_images || 1; // Calculate total images per post let imagesPerPost = 1; // Featured image (always 1) let imageTypes = ['featured']; if (desktopEnabled) { imagesPerPost += maxInArticleImages; for (let i = 1; i <= maxInArticleImages; i++) { imageTypes.push('desktop'); } } if (mobileEnabled) { imagesPerPost += maxInArticleImages; for (let i = 1; i <= maxInArticleImages; i++) { imageTypes.push('mobile'); } } const totalImages = postIds.length * imagesPerPost; console.log('Igny8: Image generation settings:', { desktopEnabled, mobileEnabled, maxInArticleImages, imagesPerPost, totalImages, imageTypes }); // Show progress modal with calculated total showProgressModal('Generate Images', totalImages, 'images'); updateProgressModal(0, totalImages, 'processing', 'Preparing to generate images...'); let totalImagesGenerated = 0; let totalImagesFailed = 0; const allResults = { generated: [], failed: [] }; // Process each post sequentially function processNextPost(postIndex) { if (postIndex >= postIds.length) { // All posts processed - show final results console.log('Igny8: All posts processed', allResults); if (allResults.generated.length > 0) { updateProgressModal(totalImages, totalImages, 'completed'); setTimeout(() => { showSuccessModal( 'Images Generated', allResults.generated.length, `Successfully generated ${allResults.generated.length} images for ${postIds.length} posts` ); // Reload table if (window.loadTableData && window.IGNY8_PAGE?.tableId) { window.loadTableData(window.IGNY8_PAGE.tableId); } }, 500); } else { if (currentProgressModal) { currentProgressModal.remove(); currentProgressModal = null; } igny8GlobalNotification('No images were generated', 'error'); } return; } const postId = postIds[postIndex]; // Update progress for current post const currentProgress = postIndex * imagesPerPost; updateProgressModal(currentProgress, totalImages, 'processing', `Post ${postIndex + 1} of ${postIds.length}`); // Generate all images for this post (one at a time) generateAllImagesForPost(postId, postIndex + 1, function(postResults) { // Post complete - add results allResults.generated.push(...postResults.generated); allResults.failed.push(...postResults.failed); totalImagesGenerated += postResults.generated.length; totalImagesFailed += postResults.failed.length; console.log(`✓ Post ${postId} complete: ${postResults.generated.length} images generated, ${postResults.failed.length} failed`); // Move to next post setTimeout(() => processNextPost(postIndex + 1), 100); }); } // Generate all images for a single post (featured + in-article) function generateAllImagesForPost(postId, postNumber, callback) { // Use the existing action but with single post const formData = new FormData(); formData.append('action', 'igny8_ai_generate_images_drafts'); formData.append('nonce', window.IGNY8_PAGE.nonce); formData.append('post_ids', JSON.stringify([postId])); formData.append('desktop_enabled', desktopEnabled ? '1' : '0'); formData.append('mobile_enabled', mobileEnabled ? '1' : '0'); formData.append('max_in_article_images', maxInArticleImages); console.log('Igny8: Sending AJAX request to:', window.IGNY8_PAGE.ajaxUrl); console.log('Igny8: AJAX request data:', { action: 'igny8_ai_generate_images_drafts', post_ids: JSON.stringify([postId]), desktop_enabled: desktopEnabled ? '1' : '0', mobile_enabled: mobileEnabled ? '1' : '0', max_in_article_images: maxInArticleImages, nonce: window.IGNY8_PAGE.nonce }); fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => { console.log('Igny8: AJAX response received:', response.status, response.statusText); return response.json(); }) .then(data => { if (data.success) { callback({ generated: data.data.generated_images || [], failed: data.data.failed_images || [] }); } else { callback({ generated: [], failed: [{ post_id: postId, error: data.data?.message || 'Unknown error' }] }); } }) .catch(error => { console.error(`✗ Post ${postId} - Exception:`, error); callback({ generated: [], failed: [{ post_id: postId, error: error.message }] }); }); } // Start processing first post processNextPost(0); } // Process AI Content Generation function processAIContentGeneration(taskId) { const formData = new FormData(); formData.append('action', 'igny8_ai_generate_content'); formData.append('nonce', window.IGNY8_PAGE.nonce); formData.append('task_id', taskId); // Show progress modal showProgressModal('Generate Draft', 1); // Log client-side event start console.log('Igny8 AI: Starting content generation for task', taskId); fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { console.log('Igny8 AI: Content generation completed successfully', data.data); // Show success modal showSuccessModal('Draft Generated', 1, data.data.message); // Reload table data try { if (window.loadTableData && window.IGNY8_PAGE?.tableId) { window.loadTableData(window.IGNY8_PAGE.tableId); } } catch (error) { console.error('Error reloading table data:', error); // Don't show error to user since content generation was successful } } else { console.error('Igny8 AI: Content generation failed', data.data); // Close progress modal and show error if (currentProgressModal) { currentProgressModal.remove(); currentProgressModal = null; } igny8GlobalNotification(data.data?.message || 'AI content generation failed', 'error'); } }) .catch(error => { console.error('Error processing AI content generation:', error); // Close progress modal and show error if (currentProgressModal) { currentProgressModal.remove(); currentProgressModal = null; } igny8GlobalNotification('Error processing AI content generation', 'error'); }); } // Unified Global Notification System function igny8GlobalNotification(message, type = 'info') { // Debug logging console.log('igny8GlobalNotification called:', message, type, new Date().toLocaleTimeString()); // Get or create global notification container let container = document.getElementById('igny8-global-notification'); if (!container) { container = document.createElement('div'); container.id = 'igny8-global-notification'; document.body.appendChild(container); } // Clear any existing timeouts to prevent conflicts if (container._hideTimeout) { clearTimeout(container._hideTimeout); console.log('Cleared existing hide timeout'); } if (container._removeTimeout) { clearTimeout(container._removeTimeout); console.log('Cleared existing remove timeout'); } // Clear any existing classes and content container.className = ''; container.textContent = message; container.style.display = 'block'; // Add type class and show container.classList.add(type); container.classList.add('show'); console.log('Notification shown, will hide in 4 seconds'); // Auto-hide after 4 seconds (4000ms) container._hideTimeout = setTimeout(() => { console.log('Starting hide animation'); container.classList.remove('show'); container.classList.add('hide'); // Remove from DOM after animation completes container._removeTimeout = setTimeout(() => { console.log('Removing notification from DOM'); container.classList.remove('hide'); container.style.display = 'none'; }, 300); }, 4000); } // Legacy function for backward compatibility /* ========================================= Debug Toggle Functionality ========================================= */ window.initializeDebugToggle = function() { const saveButton = document.getElementById('save-debug-setting'); const enabledRadio = document.getElementById('debug-enabled'); const disabledRadio = document.getElementById('debug-disabled'); if (!saveButton || !enabledRadio || !disabledRadio) return; saveButton.addEventListener('click', function() { const isEnabled = enabledRadio.checked; console.log('DEBUG: Save button clicked, isEnabled:', isEnabled); // Show loading state const originalText = this.innerHTML; this.innerHTML = ' Saving...'; this.disabled = true; // Send AJAX request to update the setting const formData = new FormData(); formData.append('action', 'igny8_toggle_debug_monitoring'); formData.append('nonce', igny8_ajax.nonce); formData.append('is_enabled', isEnabled ? '1' : '0'); console.log('DEBUG: Sending AJAX with is_enabled:', isEnabled ? '1' : '0'); fetch(igny8_ajax.ajax_url, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { // Show success notification if (typeof igny8ShowNotification === 'function') { igny8ShowNotification( isEnabled ? 'Debug monitoring enabled' : 'Debug monitoring disabled', 'success' ); } // Update module debug visibility on submodule pages updateModuleDebugVisibility(isEnabled); // Update button to show success this.innerHTML = ' Saved'; setTimeout(() => { this.innerHTML = originalText; }, 2000); } else { // Show error notification if (typeof igny8ShowNotification === 'function') { igny8ShowNotification( data.data?.message || 'Failed to update debug monitoring setting', 'error' ); } } }) .catch(error => { // Show error notification if (typeof igny8ShowNotification === 'function') { igny8ShowNotification('Network error occurred', 'error'); } }) .finally(() => { // Restore button state this.disabled = false; if (this.innerHTML.includes('Saving...')) { this.innerHTML = originalText; } }); }); }; window.updateModuleDebugVisibility = function(isEnabled) { // Only update on submodule pages (pages with module debug container) const moduleDebugContainer = document.getElementById('igny8-module-debug-container'); if (!moduleDebugContainer) return; if (isEnabled) { moduleDebugContainer.style.display = 'block'; } else { moduleDebugContainer.style.display = 'none'; } }; // Initialize module debug visibility on page load window.initializeModuleDebugVisibility = function() { const enabledRadio = document.getElementById('debug-enabled'); const moduleDebugContainer = document.getElementById('igny8-module-debug-container'); if (enabledRadio && moduleDebugContainer) { const isEnabled = enabledRadio.checked; updateModuleDebugVisibility(isEnabled); } }; /* ========================================= Igny8 Core Initialization & Dropdowns ========================================= */ jQuery(document).ready(function ($) { initializeDropdowns($); initializeDebugSystem(); initializeDebugToggle(); initializeModuleDebugVisibility(); // Removed initializeDelegatedEvents() - handled in DOMContentLoaded }); /* ========================================= Dropdown Functionality ========================================= */ window.initializeDropdowns = function ($) { // Toggle dropdown $(document).off('click.dropdown').on('click.dropdown', '.select-btn', function (e) { e.preventDefault(); e.stopPropagation(); const $select = $(this).closest('.select'); if (!$select.find('.select-list').length) return; $('.select').not($select).removeClass('open'); $select.toggleClass('open'); }); // Select item $(document).off('click.dropdown-item').on('click.dropdown-item', '.select-item', function (e) { e.stopPropagation(); const $item = $(this); const $select = $item.closest('.select'); const $btn = $select.find('.select-btn'); const value = $item.data('value'); const text = $item.text(); $btn.attr('data-value', value).data('value', value) .find('.select-text').text(text); $select.removeClass('open'); $select[0].dispatchEvent(new CustomEvent('change', { detail: { value, text } })); }); // Close on outside click $(document).off('click.dropdown-outside').on('click.dropdown-outside', function (e) { if (!$(e.target).closest('.select').length) $('.select').removeClass('open'); }); }; /* ========================================= Igny8 Debug System – Toggle & Monitoring ========================================= */ function initializeDebugSystem() { const toggle = document.getElementById('igny8-debug-toggle'); const statusText = document.getElementById('igny8-module-debug-status'); const container = document.getElementById('igny8-module-debug-container'); const widgets = document.getElementById('igny8-module-debug-widgets'); // ---- Main Debug Toggle ---- if (toggle) { setDebugUI(toggle.checked); toggle.addEventListener('change', () => { const enabled = toggle.checked; setDebugUI(enabled); saveDebugState(enabled); }); } // ---- Module Debug Widget Toggle ---- window.igny8ToggleModuleDebug = () => { if (!widgets) return; const hidden = widgets.classList.contains('hidden') || widgets.style.display === 'none'; widgets.style.display = hidden ? 'block' : 'none'; widgets.classList.toggle('hidden', !hidden); }; // ---- Auto-show Debug Panel on DOM Ready ---- document.addEventListener('DOMContentLoaded', () => { if (container && widgets) { container.style.display = 'block'; widgets.style.display = 'block'; widgets.classList.remove('hidden'); } }); // ---- Helper: UI Update ---- function setDebugUI(enabled) { if (container) container.style.display = enabled ? 'block' : 'none'; if (statusText) statusText.textContent = enabled ? 'Enabled' : 'Disabled'; } // ---- Helper: Save State via AJAX ---- function saveDebugState(enabled) { if (!igny8_ajax?.ajax_url) return; fetch(igny8_ajax.ajax_url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ action: 'igny8_toggle_debug', nonce: igny8_ajax.nonce, enabled: enabled ? '1' : '0' }) }) .then(r => r.json()) .then(d => { if (!d.success) {} }) .catch(err => {}); } } /* ========================================= Igny8 Utility – Number, Currency, Date ========================================= */ window.igny8FormatNumber = (num, decimals = 0) => new Intl.NumberFormat(undefined, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }).format(num); window.igny8FormatCurrency = (amount, currency = 'USD') => new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount); window.igny8FormatDate = (date, options = {}) => { const base = { year: 'numeric', month: 'short', day: 'numeric' }; return new Intl.DateTimeFormat('en-US', { ...base, ...options }).format(new Date(date)); }; // ---- Update Debug Grid Cell State ---- window.igny8DebugState = function (stage, ok, msg = '') { const cell = document.querySelector(`[data-debug-stage="${stage}"]`); if (!cell) return; cell.classList.toggle('ok', !!ok); cell.classList.toggle('fail', !ok); cell.classList.toggle('neutral', !ok && !ok); if (msg) cell.title = msg; }; // ---- Centralized Event Logger ---- window.igny8LogEvent = function (eventType, status, details = '') { const timestamp = new Date().toLocaleTimeString(); const logEntry = `[${timestamp}] ${eventType}: ${status}${details ? ' - ' + details : ''}`; const colorMap = { SUCCESS: '#28a745', FOUND: '#28a745', EXISTS: '#28a745', WORKING: '#28a745', MISSING: '#dc3545', BROKEN: '#dc3545', ERROR: '#dc3545', NOT_: '#dc3545', WARNING: '#ffc107', PENDING: '#ffc107' }; const color = colorMap[status] || '#6c757d'; const method = (status === 'MISSING' || status === 'BROKEN' || status === 'ERROR' || status === 'NOT_') ? 'error' : (status === 'WARNING' || status === 'PENDING') ? 'warn' : 'log'; console[method](`%c${logEntry}`, `color: ${color}; font-weight: bold;`); window.igny8EventLog = window.igny8EventLog || []; window.igny8EventLog.push(logEntry); if (window.igny8EventLog.length > 100) window.igny8EventLog = window.igny8EventLog.slice(-100); }; /* ========================================= Igny8 Filters – Inputs, Dropdowns, Ranges ========================================= */ // ---- Initialize All Filter Listeners ---- function initializeFilters() { const filters = document.querySelectorAll('.igny8-filters [data-filter]'); let boundCount = 0; filters.forEach(el => { if (el.tagName === 'INPUT' && el.type === 'text') { // Debounced search input let timer; el.addEventListener('input', () => { clearTimeout(timer); timer = setTimeout(() => { const tableId = el.closest('.igny8-filters')?.dataset.table; if (tableId) handleFilterChange(tableId); }, 400); }); boundCount++; } else if (el.classList.contains('select')) { // Dropdown select el.querySelectorAll('.select-item').forEach(item => { item.addEventListener('click', () => { const val = item.dataset.value || ''; const txt = item.textContent.trim(); const btn = el.querySelector('.select-btn'); const label = el.querySelector('.select-text'); if (btn && label) { btn.dataset.value = val; label.textContent = txt; } el.classList.remove('open'); }); }); boundCount++; } }); // ---- Range OK & Clear ---- document.querySelectorAll('[id$="_ok"]').forEach(btn => { btn.addEventListener('click', () => { const range = btn.closest('.select'); if (!range) return; const min = range.querySelector('input[id$="_min"]')?.value.trim(); const max = range.querySelector('input[id$="_max"]')?.value.trim(); const label = range.querySelector('.select-text'); if (label) { label.textContent = min && max ? `${min} - ${max}` : min ? `${min} - ∞` : max ? `0 - ${max}` : 'Volume'; } range.classList.remove('open'); }); boundCount++; }); document.querySelectorAll('[id$="_clear"]').forEach(btn => { btn.addEventListener('click', () => { const range = btn.closest('.select'); if (!range) return; range.querySelectorAll('input[type="number"]').forEach(i => i.value = ''); const label = range.querySelector('.select-text'); if (label) label.textContent = 'Volume'; range.classList.remove('open'); }); boundCount++; }); // ---- Apply & Reset ---- document.querySelectorAll('[id$="_filter_apply_btn"]').forEach(btn => { btn.addEventListener('click', () => applyFilters(btn.id.replace('_filter_apply_btn', ''))); boundCount++; }); document.querySelectorAll('[id$="_filter_reset_btn"]').forEach(btn => { btn.addEventListener('click', () => resetFilters(btn.id.replace('_filter_reset_btn', ''))); boundCount++; }); } // ---- Collect Filter Values ---- function collectFilters(tableId) { const container = document.querySelector(`[data-table="${tableId}"].igny8-filters`); if (!container) return {}; const payload = {}; container.querySelectorAll('input[data-filter]').forEach(i => { const val = i.value.trim(); if (val) payload[i.dataset.filter] = val; }); container.querySelectorAll('.select[data-filter]').forEach(s => { const val = s.querySelector('.select-btn')?.dataset.value; if (val) payload[s.dataset.filter] = val; }); container.querySelectorAll('input[type="number"]').forEach(i => { const val = i.value.trim(); if (val) { if (i.id.includes('search_volume_min')) payload['search_volume-min'] = val; if (i.id.includes('search_volume_max')) payload['search_volume-max'] = val; } }); return payload; } // ---- Trigger Filter Change ---- function handleFilterChange(tableId) { const payload = collectFilters(tableId); loadTableData(tableId, payload, 1); } // ---- Apply Filters ---- function applyFilters(tableId) { const payload = collectFilters(tableId); const perPage = getSessionPerPage(tableId) || getDefaultPerPage(); loadTableData(tableId, payload, 1, perPage); } // ---- Reset Filters ---- function resetFilters(tableId) { const container = document.querySelector(`[data-table="${tableId}"].igny8-filters`); if (!container) return; container.querySelectorAll('input[type="text"], input[type="number"]').forEach(i => i.value = ''); container.querySelectorAll('.select').forEach(select => { const btn = select.querySelector('.select-btn'); const label = select.querySelector('.select-text'); if (btn && label) { btn.dataset.value = ''; const field = select.dataset.filter; label.textContent = field ? getFilterLabel(tableId, field) : 'All'; } select.classList.remove('open'); }); const perPage = getSessionPerPage(tableId) || getDefaultPerPage(); loadTableData(tableId, {}, 1, perPage); } // ---- Retrieve Original Filter Label ---- function getFilterLabel(tableId, field) { if (window.IGNY8_PAGE?.filtersConfig?.[tableId]?.[field]) { return IGNY8_PAGE.filtersConfig[tableId][field].label || field; } const el = document.querySelector(`[data-table="${tableId}"].igny8-filters [data-filter="${field}"]`); const allText = el?.querySelector('.select-item[data-value=""]')?.textContent.trim(); const match = allText?.match(/^All\s+(.+)$/); return match ? match[1] : field.charAt(0).toUpperCase() + field.slice(1); } /* ========================================= Igny8 Table Actions – Bulk & Row Controls ========================================= */ function initializeTableActions(tableId) { const exportBtn = document.getElementById(`${tableId}_export_btn`); const deleteBtn = document.getElementById(`${tableId}_delete_btn`); const publishBtn = document.getElementById(`${tableId}_publish_btn`); const importBtn = document.getElementById(`${tableId}_import_btn`); const addBtn = document.getElementById(`${tableId}_add_btn`); const countSpan = document.getElementById(`${tableId}_count`); // ---- Button Handlers ---- exportBtn?.addEventListener('click', () => { const selectedIds = getSelectedRows(tableId); if (selectedIds.length) { igny8ShowExportModal(tableId, selectedIds); } else { igny8ShowNotification('Please select records to export', 'warning'); } }); deleteBtn?.addEventListener('click', () => { const rows = getSelectedRows(tableId); if (rows.length) deleteSelectedData(tableId, rows); else igny8ShowNotification('Please select records to delete', 'warning'); }); // Publish button handler (for drafts) publishBtn?.addEventListener('click', () => { const rows = getSelectedRows(tableId); if (rows.length) { handleBulkPublishDrafts(rows, tableId); } else { igny8ShowNotification('Please select drafts to publish', 'warning'); } }); importBtn?.addEventListener('click', () => igny8ShowImportModal(tableId)); addBtn?.addEventListener('click', () => openAddNewModal?.(tableId)); // ---- Selection Count & Button State ---- const updateStates = () => { const count = getSelectedRows(tableId).length; if (countSpan) { countSpan.textContent = `${count} selected`; countSpan.classList.toggle('igny8-count-hidden', count === 0); } if (exportBtn) exportBtn.disabled = !count; if (deleteBtn) deleteBtn.disabled = !count; if (publishBtn) publishBtn.disabled = !count; // Update AI action buttons const clusterBtn = document.getElementById(`${tableId}_ai_cluster_btn`); const ideasBtn = document.getElementById(`${tableId}_ai_ideas_btn`); const mappingBtn = document.getElementById(`${tableId}_ai_mapping_btn`); const queueWriterBtn = document.getElementById(`${tableId}_queue_writer_btn`); const generateContentBtn = document.getElementById(`${tableId}_generate_content_btn`); const generateImagesBtn = document.getElementById(`${tableId}_generate_images_btn`); if (clusterBtn) clusterBtn.disabled = !count; if (ideasBtn) ideasBtn.disabled = !count; if (mappingBtn) mappingBtn.disabled = !count; if (queueWriterBtn) queueWriterBtn.disabled = !count; if (generateContentBtn) generateContentBtn.disabled = !count; if (generateImagesBtn) generateImagesBtn.disabled = !count; }; document.addEventListener('rowSelectionChanged', e => { if (e.detail.tableId === tableId) updateStates(); }); // Initial state update updateStates(); } // ---- Helpers ---- function getSelectedRows(tableId) { return [...document.querySelectorAll(`#table-${tableId}-body input[type="checkbox"]:checked`)] .map(cb => { const row = cb.closest('tr'); return row ? row.getAttribute('data-id') : null; }) .filter(id => id !== null); } function exportSelectedData(tableId, rows) { loadTableData(tableId, {}, 1); } function deleteSelectedData(tableId, rows) { if (!rows.length) return igny8ShowNotification('No records selected for deletion', 'warning'); // Get the actual record data for the selected rows const data = rows.map(rowId => { const row = document.querySelector(`#table-${tableId}-body tr[data-id="${rowId}"]`); if (!row) return null; // Extract record data from the row const cells = row.querySelectorAll('td'); const record = { id: rowId, table_id: tableId // CRITICAL FIX: Include table_id in record data }; // Get the first text cell as the title/name if (cells.length > 1) { const titleCell = cells[1]; // Skip checkbox column record.name = titleCell.textContent.trim(); record.keyword = titleCell.textContent.trim(); // For keywords record.idea_title = titleCell.textContent.trim(); // For ideas } return record; }).filter(record => record !== null); igny8ShowDeleteDialog('bulk', data); } function deleteRow(rowId) { const row = document.querySelector(`tr[data-id="${rowId}"]`); const tableId = row?.closest('table')?.id.replace('_table', ''); if (!tableId) return; const rowData = getUniversalRowData(tableId, rowId); if (rowData) igny8ShowDeleteDialog('single', [rowData]); } /* ========================================= Igny8 Add/Edit Row Handlers ========================================= */ function openAddNewRow(tableId) { document.querySelector('tr.igny8-inline-form-row')?.remove(); const formData = new FormData(); formData.append('action', 'igny8_render_form_row'); formData.append('nonce', igny8_ajax.nonce); formData.append('table_id', tableId); formData.append('mode', 'add'); formData.append('record_data', '{}'); fetch(ajaxurl, { method: 'POST', body: formData }) .then(r => r.json()) .then(result => { if (!result.success) throw new Error(result.data); const tableBody = document.querySelector(`#table-${tableId}-body`); if (tableBody) tableBody.insertAdjacentHTML('afterbegin', result.data); }) .catch(err => igny8ShowNotification(`Add form error: ${err.message}`, 'error')); } function editRow(rowId, tableId) { document.querySelector('tr.igny8-inline-form-row')?.remove(); const formData = new FormData(); formData.append('action', 'igny8_get_row_data'); formData.append('nonce', igny8_ajax.nonce); formData.append('table_id', tableId); formData.append('row_id', rowId); fetch(ajaxurl, { method: 'POST', body: formData }) .then(r => r.json()) .then(result => { if (!result.success) throw new Error(result.data); const editFormData = new FormData(); editFormData.append('action', 'igny8_render_form_row'); editFormData.append('nonce', igny8_ajax.nonce); editFormData.append('table_id', tableId); editFormData.append('mode', 'edit'); editFormData.append('record_data', JSON.stringify(result.data)); return fetch(ajaxurl, { method: 'POST', body: editFormData }); }) .then(r => r.json()) .then(formResult => { if (!formResult.success) throw new Error(formResult.data); const row = document.querySelector(`tr[data-id="${rowId}"]`); if (row) row.outerHTML = formResult.data; else igny8ShowNotification('Target row not found for editing', 'error'); }) .catch(err => igny8ShowNotification(`Edit form error: ${err.message}`, 'error')); } /* ========================================= Igny8 Delegated Event Handlers ========================================= */ function initializeDelegatedEvents() { // Single delegated click handler to prevent conflicts document.addEventListener('click', e => { // Prevent multiple event handlers from interfering if (e.defaultPrevented) return; const addBtn = e.target.closest('[data-action="addRow"]'); const editBtn = e.target.closest('[data-action="editRow"]'); const queueBtn = e.target.closest('[data-action="queueToWriter"]'); const bulkQueueBtn = e.target.closest('[data-action="bulkQueueToWriter"]'); const deleteBtn = e.target.closest('[data-action="deleteRow"]'); const personalizeBtn = e.target.closest('#igny8-launch'); const personalizeForm = e.target.closest('#igny8-form'); const saveBtn = e.target.closest('.igny8-save-btn'); const cronBtn = e.target.closest('button[data-action]'); const descriptionToggle = e.target.closest('.igny8-description-toggle'); const imagePromptsToggle = e.target.closest('.igny8-image-prompts-toggle'); if (addBtn) { e.preventDefault(); e.stopPropagation(); openAddNewRow(addBtn.dataset.tableId); return; } if (editBtn) { e.preventDefault(); e.stopPropagation(); editRow(editBtn.dataset.rowId, editBtn.dataset.tableId); return; } if (queueBtn) { e.preventDefault(); e.stopPropagation(); const ideaId = queueBtn.dataset.ideaId; if (ideaId) { igny8QueueIdeaToWriter(ideaId); } return; } if (bulkQueueBtn) { e.preventDefault(); e.stopPropagation(); const tableId = bulkQueueBtn.closest('[data-table]')?.dataset?.table; if (tableId) { const selectedRows = getSelectedRows(tableId); if (selectedRows.length > 0) { igny8BulkQueueIdeasToWriter(selectedRows); } else { igny8ShowNotification('Please select ideas to queue to Writer', 'warning'); } } return; } if (deleteBtn) { e.preventDefault(); e.stopPropagation(); const tableId = deleteBtn.closest('[data-table]').dataset.table; const rowId = deleteBtn.dataset.rowId; const rowData = getUniversalRowData(tableId, rowId); if (rowData) igny8ShowDeleteDialog('single', [rowData]); return; } // Handle Queue to Writer button click (for Ideas table) const queueWriterBtn = e.target.closest(`[id$="_queue_writer_btn"]`); if (queueWriterBtn) { e.preventDefault(); e.stopPropagation(); const tableId = queueWriterBtn.closest('[data-table]')?.dataset?.table; if (tableId) { const selectedRows = getSelectedRows(tableId); if (selectedRows.length > 0) { if (selectedRows.length > 50) { igny8ShowNotification('Maximum 50 ideas allowed for queuing', 'error'); return; } igny8BulkQueueIdeasToWriter(selectedRows); } else { igny8ShowNotification('Please select ideas to queue to Writer', 'warning'); } } } // Handle Generate Content button click (for Writer Queue table) const generateContentBtn = e.target.closest(`[id$="_generate_content_btn"]`); if (generateContentBtn) { e.preventDefault(); e.stopPropagation(); const tableId = generateContentBtn.closest('[data-table]')?.dataset?.table; if (tableId) { const selectedRows = getSelectedRows(tableId); if (selectedRows.length > 0) { if (selectedRows.length > 5) { igny8ShowNotification('Maximum 5 tasks allowed for content generation', 'error'); return; } igny8BulkGenerateContent(selectedRows); } else { igny8ShowNotification('Please select tasks to generate content', 'warning'); } } return; } // Handle personalization button clicks if (personalizeBtn) { e.preventDefault(); e.stopPropagation(); handlePersonalizeClick(personalizeBtn); return; } // Handle personalization form submissions if (personalizeForm) { e.preventDefault(); e.stopPropagation(); handlePersonalizeFormSubmit(personalizeForm); return; } // Handle save content button if (saveBtn) { e.preventDefault(); e.stopPropagation(); handleSaveContent(saveBtn); return; } // Handle cron job buttons if (cronBtn) { e.preventDefault(); e.stopPropagation(); const action = cronBtn.getAttribute('data-action'); const hook = cronBtn.getAttribute('data-hook'); if (action === 'runNow') { handleIconRunNow(cronBtn, hook); } else if (action === 'openInNewWindow') { handleOpenInNewWindow(cronBtn, hook); } return; } // Handle description toggle clicks if (descriptionToggle) { e.preventDefault(); e.stopPropagation(); // Description toggle logic would go here return; } // Handle image prompts toggle clicks if (imagePromptsToggle) { e.preventDefault(); e.stopPropagation(); // Image prompts toggle logic would go here return; } }); } // === Planner → Writer Bridge Functions === /** * Queue a single idea to Writer */ function igny8QueueIdeaToWriter(ideaId) { const data = new FormData(); data.append('action', 'igny8_create_task_from_idea'); data.append('nonce', igny8_ajax.nonce); data.append('idea_id', ideaId); fetch(igny8_ajax.ajax_url, { method: 'POST', body: data }) .then(r => r.json()) .then(resp => { if (resp.success) { igny8ShowNotification(resp.message || 'Task created', 'success'); } else { igny8ShowNotification(resp.message || 'Failed to create task', 'error'); } // Reload both tables to show updated data if (typeof igny8ReloadTable === 'function') { igny8ReloadTable('planner_ideas'); igny8ReloadTable('writer_drafts'); } }) .catch(error => { console.error('Queue to Writer error:', error); igny8ShowNotification('Failed to queue idea to Writer', 'error'); }); } /** * Bulk generate content for tasks */ function igny8BulkGenerateContent(selectedIds) { let completed = 0; let failed = 0; const total = selectedIds.length; igny8ShowNotification(`Starting content generation for ${total} tasks...`, 'info'); selectedIds.forEach((taskId, index) => { // Add a small delay between requests to avoid overwhelming the API setTimeout(() => { const data = new FormData(); data.append('action', 'igny8_ai_generate_content'); data.append('nonce', window.IGNY8_PAGE?.nonce || igny8_ajax.nonce); data.append('task_id', taskId); fetch(window.IGNY8_PAGE?.ajaxUrl || igny8_ajax.ajax_url, { method: 'POST', body: data }) .then(r => { if (!r.ok) { throw new Error(`HTTP error! status: ${r.status}`); } return r.text().then(text => { try { return JSON.parse(text); } catch (e) { console.error('Invalid JSON response:', text); throw new Error('Invalid JSON response from server'); } }); }) .then(resp => { completed++; if (resp.success) { const data = resp.data; console.log(`Content generated for task ${taskId}:`, data); // Show detailed success message if (data.post_id) { console.log(`✅ WordPress post created: Post ID ${data.post_id}`); if (data.post_edit_url) { console.log(`📝 Edit post: ${data.post_edit_url}`); } } if (data.task_status) { console.log(`📋 Task status updated to: ${data.task_status}`); } } else { failed++; console.error(`Failed to generate content for task ${taskId}:`, resp.data); } // Check if all requests are complete if (completed + failed === total) { if (failed === 0) { igny8ShowNotification(`Content generated successfully for all ${total} tasks. Check WordPress Posts for drafts.`, 'success'); } else { igny8ShowNotification(`Content generation completed: ${completed} successful, ${failed} failed`, 'warning'); } // Reload tables to show updated data if (typeof igny8ReloadTable === 'function') { igny8ReloadTable('writer_queue'); igny8ReloadTable('writer_drafts'); } } }) .catch(error => { failed++; console.error(`Content generation error for task ${taskId}:`, error); // Check if all requests are complete if (completed + failed === total) { igny8ShowNotification(`Content generation completed: ${completed} successful, ${failed} failed`, 'warning'); // Reload tables to show updated data if (typeof igny8ReloadTable === 'function') { igny8ReloadTable('writer_queue'); igny8ReloadTable('writer_drafts'); } } }); }, index * 1000); // 1 second delay between requests }); } /** * Bulk queue ideas to Writer */ function igny8BulkQueueIdeasToWriter(selectedIds) { const data = new FormData(); data.append('action', 'igny8_bulk_create_tasks_from_ideas'); data.append('nonce', igny8_ajax.nonce); selectedIds.forEach(id => data.append('idea_ids[]', id)); // Show progress modal showProgressModal('Queue to Writer', selectedIds.length); fetch(igny8_ajax.ajax_url, { method: 'POST', body: data }) .then(r => { if (!r.ok) { throw new Error(`HTTP error! status: ${r.status}`); } return r.text().then(text => { try { return JSON.parse(text); } catch (e) { console.error('Invalid JSON response:', text); throw new Error('Invalid JSON response from server'); } }); }) .then(resp => { if (resp.success) { // Show success modal showSuccessModal('Queue to Writer Complete', selectedIds.length, resp.data?.message || 'Ideas queued to Writer successfully'); } else { // Close progress modal and show error if (currentProgressModal) { currentProgressModal.remove(); currentProgressModal = null; } igny8ShowNotification(resp.data?.message || 'Failed to queue ideas to Writer', 'error'); } // Reload both tables to show updated data if (typeof igny8ReloadTable === 'function') { igny8ReloadTable('planner_ideas'); igny8ReloadTable('writer_queue'); } }) .catch(error => { console.error('Bulk queue to Writer error:', error); // Close progress modal and show error if (currentProgressModal) { currentProgressModal.remove(); currentProgressModal = null; } igny8ShowNotification('Failed to bulk queue ideas to Writer: ' + error.message, 'error'); }); } // === Bulk & Row Actions === /** * Handle bulk actions (delete, map, unmap) */ function handleBulkAction(action, btn) { const tableId = btn.closest('[data-table]')?.dataset?.table; if (!tableId) { igny8ShowNotification('Table not found', 'error'); return; } // Get selected row IDs const selectedRows = getSelectedRows(tableId); if (selectedRows.length === 0) { igny8ShowNotification('Please select records to perform this action', 'warning'); return; } // Handle different bulk actions if (action === 'bulk_delete_keywords') { handleBulkDelete(selectedRows, tableId); } else if (action === 'bulk_map_keywords') { handleBulkMap(selectedRows, tableId); } else if (action === 'bulk_unmap_keywords') { handleBulkUnmap(selectedRows, tableId); } else if (action === 'bulk_publish_drafts') { handleBulkPublishDrafts(selectedRows, tableId); } } /** * Handle row actions (view, map, create draft) */ function handleRowAction(action, btn) { const rowId = btn.dataset.rowId; const tableId = btn.closest('[data-table]')?.dataset?.table; if (!rowId || !tableId) { igny8ShowNotification('Row or table not found', 'error'); return; } // Handle different row actions if (action === 'view_cluster_keywords') { handleViewClusterKeywords(rowId, tableId); } else if (action === 'map_cluster_to_keywords') { handleMapClusterToKeywords(rowId, tableId); } else if (action === 'create_draft_from_idea') { handleCreateDraftFromIdea(rowId, tableId); } } /** * Handle bulk delete */ function handleBulkDelete(keywordIds, tableId) { if (!confirm(`Are you sure you want to delete ${keywordIds.length} selected keywords?`)) { return; } sendBulkAction('igny8_bulk_delete_keywords', { keyword_ids: keywordIds }, tableId); } /** * Handle bulk map to cluster */ function handleBulkMap(keywordIds, tableId) { // Get cluster selection (could be from a dropdown or modal) const clusterSelect = document.querySelector(`[data-table="${tableId}"] .cluster-select`); if (!clusterSelect) { igny8ShowNotification('No cluster selection found', 'error'); return; } const clusterId = clusterSelect.value; if (!clusterId) { igny8ShowNotification('Please select a cluster to map keywords to', 'warning'); return; } sendBulkAction('igny8_bulk_map_keywords', { keyword_ids: keywordIds, cluster_id: clusterId }, tableId); } /** * Handle bulk unmap from clusters */ function handleBulkUnmap(keywordIds, tableId) { if (!confirm(`Are you sure you want to unmap ${keywordIds.length} selected keywords from their clusters?`)) { return; } sendBulkAction('igny8_bulk_unmap_keywords', { keyword_ids: keywordIds }, tableId); } /** * Handle view cluster keywords (modal) */ function handleViewClusterKeywords(clusterId, tableId) { sendRowAction('igny8_view_cluster_keywords', { cluster_id: clusterId }, (response) => { if (response.success) { showClusterKeywordsModal(response.cluster_name, response.keywords); } }); } /** * Handle map cluster to keywords */ function handleMapClusterToKeywords(clusterId, tableId) { // Get selected keyword IDs from checkboxes const selectedRows = getSelectedRows(tableId); if (selectedRows.length === 0) { igny8ShowNotification('Please select keywords to map to this cluster', 'warning'); return; } sendRowAction('igny8_map_cluster_to_keywords', { cluster_id: clusterId, keyword_ids: selectedRows }, tableId); } /** * Handle create draft from idea */ function handleCreateDraftFromIdea(ideaId, tableId) { sendRowAction('igny8_create_draft_from_idea', { idea_id: ideaId }, (response) => { if (response.success) { igny8ShowNotification(`Draft created successfully (ID: ${response.draft_id})`, 'success'); // Optionally refresh the table to show the new draft loadTableData(tableId, {}, 1); } }); } /** * Handle bulk publish drafts action */ function handleBulkPublishDrafts(selectedRows, tableId) { if (selectedRows.length === 0) { igny8ShowNotification('Please select drafts to publish', 'warning'); return; } // Show confirmation dialog if (!confirm(`Are you sure you want to publish ${selectedRows.length} draft(s)? This will make them live on your website.`)) { return; } const formData = new FormData(); formData.append('action', 'igny8_bulk_publish_drafts'); formData.append('nonce', window.IGNY8_PAGE?.nonce || igny8_ajax.nonce); // Add task IDs selectedRows.forEach(id => { formData.append('task_ids[]', id); }); // Show progress notification igny8ShowNotification('Publishing drafts...', 'info'); fetch(window.IGNY8_PAGE?.ajaxUrl || igny8_ajax.ajax_url, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { igny8ShowNotification(data.data.message, 'success'); // Refresh table to show updated data if (window.loadTableData && tableId) { window.loadTableData(tableId); } } else { igny8ShowNotification(data.data?.message || 'Failed to publish drafts', 'error'); } }) .catch(error => { console.error('Bulk publish error:', error); igny8ShowNotification('Network error occurred while publishing', 'error'); }); } /** * Send bulk action AJAX request */ function sendBulkAction(action, data, tableId) { const formData = new FormData(); formData.append('action', action); formData.append('nonce', igny8_ajax.nonce); // Add data fields Object.keys(data).forEach(key => { if (Array.isArray(data[key])) { data[key].forEach(value => { formData.append(`${key}[]`, value); }); } else { formData.append(key, data[key]); } }); fetch(igny8_ajax.ajax_url, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { igny8ShowNotification(data.data.message, 'success'); // Show workflow automation results if available if (data.data.workflow_message) { igny8ShowNotification(data.data.workflow_message, 'info'); } // Refresh table to show updated data loadTableData(tableId, {}, 1); } else { igny8ShowNotification(data.data || 'Action failed', 'error'); } }) .catch(error => { igny8ShowNotification('Network error occurred', 'error'); }); } /** * Send row action AJAX request */ function sendRowAction(action, data, tableId, callback) { const formData = new FormData(); formData.append('action', action); formData.append('nonce', igny8_ajax.nonce); // Add data fields Object.keys(data).forEach(key => { formData.append(key, data[key]); }); fetch(igny8_ajax.ajax_url, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { if (callback) { callback(data); } else { igny8ShowNotification(data.data.message, 'success'); // Show workflow automation results if available if (data.data.workflow_message) { igny8ShowNotification(data.data.workflow_message, 'info'); } // Refresh table to show updated data if (tableId) { loadTableData(tableId, {}, 1); } } } else { igny8ShowNotification(data.data || 'Action failed', 'error'); } }) .catch(error => { igny8ShowNotification('Network error occurred', 'error'); }); } /** * Show cluster keywords modal */ function showClusterKeywordsModal(clusterName, keywords) { // Remove existing modal if present document.getElementById('cluster-keywords-modal')?.remove(); const modal = document.createElement('div'); modal.id = 'cluster-keywords-modal'; modal.className = 'igny8-modal'; modal.innerHTML = `

Keywords in "${clusterName}"

${keywords.length > 0 ? `` : '

No keywords found in this cluster.

' }
`; document.body.appendChild(modal); modal.classList.add('open'); } /* ========================================= Igny8 Pagination Component ========================================= */ function initializePagination(tableId) { const container = document.querySelector(`[data-table="${tableId}"].igny8-pagination`); if (!container) { return; } // --- Page Navigation --- container.addEventListener('click', e => { const btn = e.target.closest('.igny8-page-btn'); if (!btn) return; const page = parseInt(btn.dataset.page); if (!page || page === getCurrentPage(tableId)) return; const filters = collectFilters(tableId); const perPage = getSessionPerPage(tableId) || getDefaultPerPage(); loadTableData(tableId, filters, page, perPage); }); // --- Per-Page Selection --- const perPageSelect = document.querySelector(`#${tableId} .igny8-per-page-select`); if (perPageSelect) { perPageSelect.addEventListener('change', () => { const perPage = parseInt(perPageSelect.value); const filters = collectFilters(tableId); filters.per_page = perPage; loadTableData(tableId, filters, 1); }); } } function getCurrentPage(tableId) { return parseInt(document .querySelector(`[data-table="${tableId}"].igny8-pagination`) ?.dataset.currentPage || 1); } function loadPage(tableId, page, perPage = null) { const filters = collectFilters(tableId); filters.per_page = perPage || getCurrentPerPage(tableId); loadTableData(tableId, filters, page); } function getCurrentPerPage(tableId) { return parseInt(document.querySelector(`#${tableId} .igny8-per-page-select`)?.value || 10); } /** * Update pagination controls * @param {string} tableId * @param {Object} p - Pagination data */ function updatePagination(tableId, p) { const container = document.querySelector(`[data-table="${tableId}"].igny8-pagination`); if (!container) return; container.dataset.currentPage = p.current_page || 1; container.dataset.totalItems = p.total_items || 0; container.dataset.perPage = p.per_page || 10; container.innerHTML = ''; if (!p.total_pages) return; const { current_page, total_pages, total_items, per_page } = p; // --- Prev Button --- if (current_page > 1) addPageBtn(container, '‹ Previous', current_page - 1); // --- First Pages --- for (let i = 1; i <= Math.min(2, total_pages); i++) addPageBtn(container, i, i, i === current_page); // --- Ellipsis before middle --- if (total_pages > 4 && current_page > 3) addEllipsis(container); // --- Middle Page --- if (current_page > 2 && current_page < total_pages - 1) addPageBtn(container, current_page, current_page, true); // --- Ellipsis before last pages --- if (total_pages > 4 && current_page < total_pages - 2) addEllipsis(container); // --- Last Pages --- for (let i = Math.max(3, total_pages - 1); i <= total_pages; i++) if (i > 2) addPageBtn(container, i, i, i === current_page); // --- Next Button --- if (current_page < total_pages) addPageBtn(container, 'Next ›', current_page + 1); // --- Info Text --- const start = (current_page - 1) * per_page + 1; const end = Math.min(current_page * per_page, total_items); const info = document.createElement('span'); info.textContent = `Showing ${start}-${end} of ${total_items} items`; Object.assign(info.style, { marginLeft: '12px', fontSize: '12px', color: '#666' }); container.appendChild(info); } // --- Helpers --- function addPageBtn(container, label, page, isActive = false) { const btn = document.createElement('button'); btn.textContent = label; btn.dataset.page = page; btn.className = `igny8-btn igny8-btn-sm igny8-page-btn ${isActive ? 'igny8-btn-primary' : 'igny8-btn-outline'}`; container.appendChild(btn); } function addEllipsis(container) { const span = document.createElement('span'); span.textContent = '...'; Object.assign(span.style, { margin: '0 8px', color: '#666' }); container.appendChild(span); } /* ========================================= Igny8 Delete Dialog & Notifications ========================================= */ function igny8ShowDeleteDialog(type, records) { // Remove existing modal if present document.getElementById('igny8-delete-modal')?.remove(); const modal = document.createElement('div'); modal.id = 'igny8-delete-modal'; modal.className = 'igny8-modal'; const headerTitle = type === 'single' ? 'Delete Record' : 'Delete Multiple Records'; const bodyHTML = type === 'single' ? getSingleDeleteBody(records[0]) : getBulkDeleteBody(records); modal.innerHTML = `

${headerTitle}

${bodyHTML}
`; document.body.appendChild(modal); modal.classList.add('open'); // Store delete context window.igny8DeleteRecords = records; window.igny8DeleteType = type; } // ---- Helper to build single-record body ---- function getSingleDeleteBody(record) { const title = record.keyword || record.name || record.idea_title || 'Unknown'; return `

Are you sure you want to delete this record?

${title}

This action cannot be undone.

`; } // ---- Helper to build bulk delete body ---- function getBulkDeleteBody(records) { const total = records.length; const previewCount = Math.min(5, total); const remaining = total - previewCount; const previewItems = records.slice(0, previewCount) .map(r => `
  • ${r.keyword || r.name || r.idea_title || 'Unknown'}
  • `) .join(''); const moreText = remaining > 0 ? `
  • ... and ${remaining} more records
  • ` : ''; return `

    Are you sure you want to delete ${total} records?

    This action cannot be undone.

    `; } // ---- Confirm Deletion ---- async function igny8ConfirmDelete() { const { igny8DeleteRecords: records, igny8DeleteType: type } = window; if (!records || !type) return; const tableId = records[0].table_id || 'planner_keywords'; try { const formData = new FormData(); formData.append('nonce', igny8_ajax.nonce); formData.append('table_id', tableId); if (type === 'single') { formData.append('action', 'igny8_delete_single_record'); formData.append('record_id', records[0].id); } else { formData.append('action', 'igny8_delete_bulk_records'); records.forEach(r => formData.append('record_ids[]', r.id)); } const res = await fetch(igny8_ajax.ajax_url, { method: 'POST', body: formData }); const data = await res.json(); if (data.success) { igny8ShowNotification(data.data.message || 'Record(s) deleted successfully', 'success'); igny8CancelDelete(); loadTableData(tableId, {}, 1); if (type === 'bulk') updateBulkActionStates(tableId); } else { igny8ShowNotification(data.data || 'Delete failed', 'error'); } } catch (err) { igny8ShowNotification('Delete failed due to a server error.', 'error'); } } // ---- Cancel & Close Modal ---- function igny8CancelDelete() { document.getElementById('igny8-delete-modal')?.remove(); window.igny8DeleteRecords = null; window.igny8DeleteType = null; } // ---- Universal Bulk Action State Update ---- function updateBulkActionStates(tableId) { document.querySelectorAll(`[data-table="${tableId}"] .igny8-checkbox`) .forEach(cb => cb.checked = false); const selectAll = document.querySelector(`[data-table="${tableId}"] .igny8-select-all`); if (selectAll) { selectAll.checked = false; selectAll.indeterminate = false; } const deleteBtn = document.getElementById(`${tableId}_delete_btn`); const exportBtn = document.getElementById(`${tableId}_export_btn`); if (deleteBtn) deleteBtn.disabled = true; if (exportBtn) exportBtn.disabled = true; const countDisplay = document.querySelector(`[data-table="${tableId}"] .igny8-selected-count`); if (countDisplay) countDisplay.textContent = '0 selected'; } // ---- Unified Notification System ---- function igny8ShowNotification(message, type = 'info', tableId = null) { // Use the unified global notification system igny8GlobalNotification(message, type); } // =================================================================== // TABLE COMPONENT // =================================================================== /* ---------------------------- Table Body Update ----------------------------- */ function updateTableBody(tableId, tableBodyHtml) { const tbody = document.querySelector(`#table-${tableId}-body`); if (!tbody) return; tbody.innerHTML = ''; if (tableBodyHtml) { const template = document.createElement('template'); template.innerHTML = tableBodyHtml; template.content.querySelectorAll('tr').forEach(row => { tbody.appendChild(row.cloneNode(true)); }); } } /* ---------------------------- Loading State ----------------------------- */ function showTableLoadingState(tableId) { const tbody = document.querySelector(`#table-${tableId}-body`); if (!tbody) return; tbody.innerHTML = ''; const loadingRow = document.createElement('tr'); loadingRow.innerHTML = `Loading...`; tbody.appendChild(loadingRow); } /* ---------------------------- Detect Current Table ----------------------------- */ function detectCurrentTableId() { return ( document.querySelector('.igny8-table[data-table]')?.dataset.table || document.querySelector('.igny8-filters[data-table]')?.dataset.table || document.querySelector('.igny8-pagination[data-table]')?.dataset.table || null ); } /* ---------------------------- Unified AJAX Loader ----------------------------- */ async function loadTableData(tableId, filters = {}, page = 1, perPage = null) { try { showTableLoadingState(tableId); const recordsPerPage = perPage || getSessionPerPage(tableId) || getDefaultPerPage(); const body = new URLSearchParams({ action: 'igny8_get_table_data', nonce: igny8_ajax.nonce, table: tableId, filters: JSON.stringify(filters), page, per_page: recordsPerPage }); const response = await fetch(igny8_ajax.ajax_url, { method: 'POST', body }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); if (!data.success) throw new Error(data.data || 'Unknown error'); // Update DOM if (data.data.table_body_html) updateTableBody(tableId, data.data.table_body_html); if (data.data.pagination) updatePagination(tableId, data.data.pagination); } catch (err) { igny8ShowNotification('Failed to load table data', 'error'); } } /* ---------------------------- Universal Table Initialization ----------------------------- */ function initializeTableWithAJAX(tableId, module, submodule) { initializeFilters(); initializeTableActions(tableId); initializePagination(tableId); initializeTableSelection(tableId); loadTableData(tableId, {}, 1); } /* ---------------------------- Row Selection Handling ----------------------------- */ function initializeTableSelection(tableId) { const table = document.getElementById(tableId); if (!table) return; const selectAll = table.querySelector('thead input[type="checkbox"]'); if (selectAll) { selectAll.addEventListener('change', () => { document.querySelectorAll(`#table-${tableId}-body input[type="checkbox"]`).forEach(cb => { cb.checked = selectAll.checked; }); dispatchSelectionChange(tableId); }); } table.addEventListener('change', e => { if (e.target.matches('#table-' + tableId + '-body input[type="checkbox"]')) { const all = document.querySelectorAll(`#table-${tableId}-body input[type="checkbox"]`); const checked = document.querySelectorAll(`#table-${tableId}-body input[type="checkbox"]:checked`); if (selectAll) { selectAll.checked = checked.length === all.length; selectAll.indeterminate = checked.length > 0 && checked.length < all.length; } dispatchSelectionChange(tableId); } }); } function dispatchSelectionChange(tableId) { document.dispatchEvent(new CustomEvent('rowSelectionChanged', { detail: { tableId } })); } /* ---------------------------- Universal Row Data Extraction ----------------------------- */ function getUniversalRowData(tableId, rowId) { const row = document.querySelector(`[data-table="${tableId}"] tr[data-id="${rowId}"]`); if (!row) return null; const headers = row.closest('table').querySelectorAll('thead th'); const cells = row.querySelectorAll('td'); const record = { id: rowId, table_id: tableId }; // Map headers to cell values (skip checkbox + actions) for (let i = 1; i < headers.length - 1; i++) { const field = headers[i].textContent.trim().toLowerCase().replace(/\s+/g, '_'); record[field] = cells[i]?.textContent.trim() || ''; } if (tableId === 'planner_keywords') { const map = { volume: 'search_volume', cluster: 'cluster_id' }; return Object.keys(record).reduce((acc, key) => { acc[map[key] || key] = record[key]; return acc; }, { id: rowId, table_id: tableId }); } return record; } /* ---------------------------- Per-Page Handling ----------------------------- */ function initializePerPageSelectors() { const defaultPP = getDefaultPerPage(); document.querySelectorAll('.igny8-per-page-select').forEach(select => { const tableId = select.dataset.table; select.value = getSessionPerPage(tableId) || defaultPP; select.addEventListener('change', e => { const perPage = parseInt(e.target.value); setSessionPerPage(tableId, perPage); loadTableData(tableId, {}, 1, perPage); }); }); } const getDefaultPerPage = () => 20; const getSessionPerPage = id => sessionStorage.getItem(`igny8_per_page_${id}`); const setSessionPerPage = (id, val) => sessionStorage.setItem(`igny8_per_page_${id}`, val); /* ---------------------------- Prompts Functionality ----------------------------- */ window.initializePromptsFunctionality = function() { // Only initialize if we're on the planner home page if (!window.IGNY8_PAGE || window.IGNY8_PAGE.module !== 'planner' || window.IGNY8_PAGE.submodule !== 'home') { return; } const savePromptsBtn = document.getElementById('igny8-save-prompts'); const resetPromptsBtn = document.getElementById('igny8-reset-prompts'); if (savePromptsBtn) { savePromptsBtn.addEventListener('click', function() { const formData = new FormData(); formData.append('action', 'igny8_save_ai_prompts'); formData.append('nonce', window.IGNY8_PAGE.nonce); // Get prompt values const clusteringPrompt = document.querySelector('textarea[name="igny8_clustering_prompt"]').value; const ideasPrompt = document.querySelector('textarea[name="igny8_ideas_prompt"]').value; formData.append('igny8_clustering_prompt', clusteringPrompt); formData.append('igny8_ideas_prompt', ideasPrompt); // Show loading state const originalText = savePromptsBtn.textContent; savePromptsBtn.textContent = 'Saving...'; savePromptsBtn.disabled = true; fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { igny8GlobalNotification('Prompts saved successfully!', 'success'); } else { const errorMsg = data.data?.message || 'Error saving prompts'; igny8GlobalNotification(errorMsg, 'error'); } }) .catch(error => { console.error('Error saving prompts:', error); igny8GlobalNotification('Error saving prompts. Please try again.', 'error'); }) .finally(() => { // Reset button state savePromptsBtn.textContent = originalText; savePromptsBtn.disabled = false; }); }); } if (resetPromptsBtn) { resetPromptsBtn.addEventListener('click', function() { if (confirm('Are you sure you want to reset all prompts to their default values? This action cannot be undone.')) { const formData = new FormData(); formData.append('action', 'igny8_reset_ai_prompts'); formData.append('nonce', window.IGNY8_PAGE.nonce); // Show loading state const originalText = resetPromptsBtn.textContent; resetPromptsBtn.textContent = 'Resetting...'; resetPromptsBtn.disabled = true; fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { // Reload the page to show default prompts window.location.reload(); } else { const errorMsg = data.data?.message || 'Error resetting prompts'; igny8GlobalNotification(errorMsg, 'error'); } }) .catch(error => { console.error('Error resetting prompts:', error); igny8GlobalNotification('Error resetting prompts. Please try again.', 'error'); }) .finally(() => { // Reset button state resetPromptsBtn.textContent = originalText; resetPromptsBtn.disabled = false; }); } }); } } /* ---------------------------- DOM Ready ----------------------------- */ document.addEventListener('DOMContentLoaded', () => { initializePerPageSelectors(); // Initialize planner settings initializePlannerSettings(); // Initialize AI integration form initializeAIIntegrationForm(); // Initialize AI action buttons initializeAIActionButtons(); // Initialize prompts functionality initializePromptsFunctionality(); // Initialize Writer AI settings initializeWriterAISettings(); // Only initialize table functionality on submodule pages that have tableId if (typeof IGNY8_PAGE !== 'undefined' && IGNY8_PAGE.submodule && IGNY8_PAGE.tableId) { initializeTableWithAJAX(IGNY8_PAGE.tableId, IGNY8_PAGE.module, IGNY8_PAGE.submodule); } // No fallback initialization - tables should only be initialized on submodule pages // Initialize all delegated events in one place to prevent conflicts initializeDelegatedEvents(); // Initialize personalization functionality initializePersonalization(); }); // =================================================================== // Form functionality // =================================================================== /** * Client-side validation function for form data * Performs lightweight validation before AJAX submission * * @param {HTMLElement} formRow The form row element * @param {string} tableId The table ID for validation rules * @returns {Object} Validation result with valid boolean and error message */ function igny8ValidateFormData(formRow, tableId) { // Define validation rules for each table const validationRules = { 'planner_keywords': { 'keyword': { required: true, maxLength: 255, noHtml: true }, 'search_volume': { required: false, type: 'numeric', min: 0 }, 'difficulty': { required: false, type: 'numeric_or_text', min: 0, max: 100, textOptions: ['Very Easy', 'Easy', 'Medium', 'Hard', 'Very Hard'] }, 'cpc': { required: false, type: 'decimal', min: 0 }, 'intent': { required: false, enum: ['informational', 'navigational', 'transactional', 'commercial'] }, 'status': { required: true, enum: ['unmapped', 'mapped', 'queued', 'published'] }, 'cluster_id': { required: false, type: 'integer' } }, 'planner_clusters': { 'cluster_name': { required: true, maxLength: 255, noHtml: true }, 'sector_id': { required: false, type: 'integer' }, 'status': { required: true, enum: ['active', 'inactive', 'archived'] } }, 'planner_ideas': { 'idea_title': { required: true, maxLength: 255, noHtml: true }, 'idea_description': { required: false, noHtml: true }, 'content_structure': { required: true, enum: ['cluster_hub', 'landing_page', 'guide_tutorial', 'how_to', 'comparison', 'review', 'top_listicle', 'question', 'product_description', 'service_page', 'home_page'] }, 'content_type': { required: true, enum: ['post', 'product', 'page', 'CPT'] }, 'keyword_cluster_id': { required: false, type: 'integer' }, 'status': { required: true, enum: ['new', 'scheduled', 'published'] }, 'estimated_word_count': { required: false, type: 'integer', min: 0 }, 'target_keywords': { required: false, type: 'text', noHtml: false }, 'mapped_post_id': { required: false, type: 'integer' } }, 'writer_tasks': { 'title': { required: true, maxLength: 255, noHtml: true }, 'description': { required: false, noHtml: true }, 'status': { required: true, enum: ['pending', 'in_progress', 'completed', 'cancelled', 'draft', 'queued', 'review', 'published'] }, 'priority': { required: true, enum: ['high', 'medium', 'low', 'urgent'] }, 'content_type': { required: false, enum: ['blog_post', 'landing_page', 'product_page', 'guide_tutorial', 'news_article', 'review', 'comparison', 'email', 'social_media'] }, 'cluster_id': { required: false, type: 'integer' }, 'keywords': { required: false, type: 'text', noHtml: false }, 'word_count': { required: false, type: 'integer', min: 0 }, 'idea_id': { required: false, type: 'integer' }, 'due_date': { required: false, type: 'datetime' }, 'schedule_at': { required: false, type: 'datetime' }, 'assigned_post_id': { required: false, type: 'integer' }, 'ai_writer': { required: false, enum: ['ai', 'human'] } }, }; const rules = validationRules[tableId]; if (!rules) { return { valid: true }; // No validation rules defined, allow submission } // Get form inputs const inputs = formRow.querySelectorAll('input, textarea'); const selects = formRow.querySelectorAll('.select-btn'); // Validate each field for (const [fieldName, fieldRules] of Object.entries(rules)) { let value = ''; // Get value from input or select const input = Array.from(inputs).find(i => i.name === fieldName); if (input) { value = input.value.trim(); } else { const select = Array.from(selects).find(s => s.name === fieldName); if (select) { value = select.getAttribute('data-value') || ''; } } // Required field validation if (fieldRules.required && (!value || value === '')) { return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} is required` }; } // Skip further validation if field is empty and not required if (!value || value === '') { continue; } // Length validation if (fieldRules.maxLength && value.length > fieldRules.maxLength) { return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} cannot exceed ${fieldRules.maxLength} characters` }; } // HTML content validation if (fieldRules.noHtml && value !== value.replace(/<[^>]*>/g, '')) { return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} cannot contain HTML` }; } // Numeric validation if (fieldRules.type === 'numeric' || fieldRules.type === 'integer' || fieldRules.type === 'decimal' || fieldRules.type === 'numeric_or_text') { let numValue; // Handle numeric_or_text type (like difficulty) if (fieldRules.type === 'numeric_or_text') { if (fieldRules.textOptions && fieldRules.textOptions.includes(value)) { // Valid text option, convert to numeric for range validation const difficultyMap = { 'Very Easy': 10, 'Easy': 30, 'Medium': 50, 'Hard': 70, 'Very Hard': 90 }; numValue = difficultyMap[value] || 0; } else if (isNaN(value) || value === '') { return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} must be a number or valid difficulty level` }; } else { numValue = parseFloat(value); } } else { if (isNaN(value) || value === '') { return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} must be a number` }; } numValue = parseFloat(value); } // Range validation if (fieldRules.min !== undefined && numValue < fieldRules.min) { return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} must be at least ${fieldRules.min}` }; } if (fieldRules.max !== undefined && numValue > fieldRules.max) { return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} must be at most ${fieldRules.max}` }; } // Integer validation if ((fieldRules.type === 'integer') && !Number.isInteger(numValue)) { return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} must be a whole number` }; } } // Enum validation if (fieldRules.enum && !fieldRules.enum.includes(value)) { return { valid: false, error: `${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)} must be one of: ${fieldRules.enum.join(', ')}` }; } } return { valid: true }; } document.addEventListener('click', function (e) { const saveBtn = e.target.closest('.igny8-form-save'); const cancelBtn = e.target.closest('.igny8-form-cancel'); // Save action if (saveBtn) { const formRow = saveBtn.closest('tr.igny8-inline-form-row'); if (!formRow) return; const tableId = saveBtn.dataset.tableId; const nonce = saveBtn.dataset.nonce; const mode = formRow.dataset.mode; const recordId = formRow.dataset.id || ''; // Client-side validation before AJAX submit const validationResult = igny8ValidateFormData(formRow, tableId); if (!validationResult.valid) { if (typeof igny8ShowNotification === 'function') { igny8ShowNotification(validationResult.error, 'error'); } return; } const formData = new FormData(); formData.append('action', 'igny8_save_form_record'); formData.append('nonce', nonce); formData.append('table_id', tableId); formData.append('action_type', mode); if (recordId) formData.append('record_id', recordId); formRow.querySelectorAll('input, textarea').forEach(input => { if (input.name && input.name !== 'record_id') { formData.append(input.name, input.value); } }); formRow.querySelectorAll('.select-btn').forEach(btn => { if (btn.name) formData.append(btn.name, btn.getAttribute('data-value') || ''); }); fetch(ajaxurl, { method: 'POST', body: formData }) .then(res => res.json()) .then(result => { if (result.success) { if (typeof igny8ShowNotification === 'function') { igny8ShowNotification('Record saved successfully!', 'success', tableId); // Show workflow automation results if available if (result.data.workflow_message) { igny8ShowNotification(result.data.workflow_message, 'info', tableId); } } formRow.style.transition = 'all 0.3s ease-out'; formRow.style.opacity = '0'; formRow.style.transform = 'translateX(-20px)'; setTimeout(() => { formRow.remove(); if (typeof loadTableData === 'function') { loadTableData(tableId, {}, 1); } else { location.reload(); } }, 300); } else { igny8ShowNotification(`Error saving record: ${result.data?.message || result.data || 'Unknown error'}`, 'error', tableId); } }) .catch(err => { igny8ShowNotification(`Error saving record: ${err.message}`, 'error', tableId); }); } // Cancel action if (cancelBtn) { const formRow = cancelBtn.closest('tr.igny8-inline-form-row'); if (!formRow) return; formRow.style.transition = 'all 0.3s ease-out'; formRow.style.opacity = '0'; formRow.style.transform = 'translateX(20px)'; setTimeout(() => formRow.remove(), 300); } }); /* ========================================= Personalization Module Functionality ========================================= */ /** * Initialize personalization functionality */ function initializePersonalization() { // Personalization click handlers moved to main delegated events handler // Handle auto mode initialization const autoContainer = document.getElementById('igny8-auto-content'); if (autoContainer) { initializeAutoMode(autoContainer); } // Handle inline mode initialization const inlineContainer = document.getElementById('igny8-inline-form'); if (inlineContainer) { initializeInlineMode(inlineContainer); } } /** * Handle personalization button click */ function handlePersonalizeClick(button) { const ajaxUrl = button.dataset.ajaxUrl; const postId = button.dataset.postId; const formFields = button.dataset.formFields || ''; // Get all data attributes for context const contextData = {}; for (const [key, value] of Object.entries(button.dataset)) { if (key !== 'ajaxUrl' && key !== 'postId' && key !== 'formFields') { contextData[key] = value; } } // Build URL with context data let url = `${ajaxUrl}?action=igny8_get_fields&post_id=${postId}`; if (formFields) { url += `&form_fields=${encodeURIComponent(formFields)}`; } // Add nonce for security if (window.igny8_ajax?.nonce) { url += `&nonce=${encodeURIComponent(window.igny8_ajax.nonce)}`; } // Add context data as query parameters for (const [key, value] of Object.entries(contextData)) { url += `&${key}=${encodeURIComponent(value)}`; } // Show loading state const originalContent = button.parentElement.innerHTML; button.parentElement.innerHTML = '
    Loading personalization form...
    '; // Load form fields fetch(url, { method: 'GET', headers: { 'X-Requested-With': 'XMLHttpRequest' } }) .then(response => response.json()) .then(data => { if (data.success) { button.parentElement.innerHTML = data.data; // Re-initialize form handlers for the new content initializePersonalization(); } else { throw new Error(data.data || 'Failed to load form'); } }) .catch(error => { console.error('Error loading personalization form:', error); button.parentElement.innerHTML = originalContent; igny8ShowNotification('Error loading personalization form: ' + error.message, 'error'); }); } /** * Handle personalization form submission */ function handlePersonalizeFormSubmit(form) { const ajaxUrl = form.closest('[data-ajax-url]')?.dataset.ajaxUrl || document.querySelector('#igny8-launch')?.dataset.ajaxUrl || window.igny8_ajax?.ajax_url; const postId = form.closest('[data-post-id]')?.dataset.postId || document.querySelector('#igny8-launch')?.dataset.postId; if (!ajaxUrl || !postId) { igny8ShowNotification('Missing configuration for personalization', 'error'); return; } // Collect form data const formData = new FormData(); formData.append('action', 'igny8_generate_custom'); formData.append('nonce', window.igny8_ajax?.nonce || ''); formData.append('post_id', postId); // Add all form fields const inputs = form.querySelectorAll('input, select, textarea'); inputs.forEach(input => { if (input.name && input.name !== 'submit') { formData.append(input.name, input.value); } }); // Show loading state const outputContainer = document.getElementById('igny8-generated-content') || form.parentElement; if (outputContainer) { outputContainer.innerHTML = '
    Generating personalized content...
    '; } // Submit form fetch(ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { if (outputContainer) { outputContainer.innerHTML = `

    Your Personalized Content

    ${data.data}
    `; } igny8ShowNotification('Content personalized successfully!', 'success'); } else { throw new Error(data.data || 'Failed to generate content'); } }) .catch(error => { console.error('Error generating content:', error); if (outputContainer) { outputContainer.innerHTML = '
    Error generating personalized content: ' + error.message + '
    '; } igny8ShowNotification('Error generating personalized content', 'error'); }); } /** * Handle save content button */ function handleSaveContent(button) { const contentContainer = button.closest('.igny8-content-container'); if (!contentContainer) { igny8ShowNotification('No content to save', 'error'); return; } const content = contentContainer.querySelector('.igny8-final-content')?.innerHTML; const postId = document.querySelector('#igny8-launch')?.dataset.postId || document.querySelector('[data-post-id]')?.dataset.postId; if (!content || !postId) { igny8ShowNotification('Missing content or post ID', 'error'); return; } // Get field inputs from the form const form = document.getElementById('igny8-form'); const fieldInputs = {}; if (form) { const inputs = form.querySelectorAll('input, select, textarea'); inputs.forEach(input => { if (input.name && input.name !== 'submit' && input.name !== 'PageContent') { fieldInputs[input.name] = input.value; } }); } // Show loading state const originalText = button.innerHTML; button.innerHTML = ' Saving...'; button.disabled = true; // Save content const formData = new FormData(); formData.append('action', 'igny8_save_content_manual'); formData.append('nonce', window.igny8_ajax?.nonce || ''); formData.append('content', content); formData.append('post_id', postId); formData.append('field_inputs', JSON.stringify(fieldInputs)); fetch(window.igny8_ajax?.ajax_url || '/wp-admin/admin-ajax.php', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { igny8ShowNotification(data.data?.message || 'Content saved successfully!', 'success'); // Update the content container with saved status const statusElement = contentContainer.querySelector('.igny8-content-status'); if (statusElement) { statusElement.textContent = '✅ Content saved'; } } else { igny8ShowNotification(data.data?.message || 'Error saving content', 'error'); } }) .catch(error => { console.error('Error saving content:', error); igny8ShowNotification('Error saving content', 'error'); }) .finally(() => { button.innerHTML = originalText; button.disabled = false; }); } /** * Initialize auto mode */ function initializeAutoMode(container) { const ajaxUrl = container.dataset.ajaxUrl; const postId = container.dataset.postId; const formFields = container.dataset.formFields || ''; // Get all data attributes for context const contextData = {}; for (const [key, value] of Object.entries(container.dataset)) { if (key !== 'ajaxUrl' && key !== 'postId' && key !== 'formFields') { contextData[key] = value; } } // Build URL with context data let url = `${ajaxUrl}?action=igny8_get_fields&post_id=${postId}`; if (formFields) { url += `&form_fields=${encodeURIComponent(formFields)}`; } // Add context data as query parameters for (const [key, value] of Object.entries(contextData)) { url += `&${key}=${encodeURIComponent(value)}`; } // Load form and auto-submit fetch(url) .then(response => response.text()) .then(html => { const formContainer = document.createElement('div'); formContainer.innerHTML = html; const form = formContainer.querySelector('#igny8-form'); if (form) { // Auto-submit the form setTimeout(() => { form.dispatchEvent(new Event('submit')); }, 1000); } }) .catch(error => { console.error('Error in auto mode:', error); container.querySelector('.igny8-loading').textContent = 'Error loading personalization form'; }); } /** * Initialize inline mode */ function initializeInlineMode(container) { const ajaxUrl = document.querySelector('#igny8-launch')?.dataset.ajaxUrl || window.igny8_ajax?.ajax_url; const postId = document.querySelector('#igny8-launch')?.dataset.postId || document.querySelector('[data-post-id]')?.dataset.postId; const formFields = document.querySelector('#igny8-launch')?.dataset.formFields || ''; if (!ajaxUrl || !postId) { console.error('Missing AJAX URL or post ID for inline mode'); return; } // Get all data attributes for context const launchButton = document.querySelector('#igny8-launch'); const contextData = {}; if (launchButton) { for (const [key, value] of Object.entries(launchButton.dataset)) { if (key !== 'ajaxUrl' && key !== 'postId' && key !== 'formFields') { contextData[key] = value; } } } // Build URL with context data let url = `${ajaxUrl}?action=igny8_get_fields&post_id=${postId}`; if (formFields) { url += `&form_fields=${encodeURIComponent(formFields)}`; } // Add context data as query parameters for (const [key, value] of Object.entries(contextData)) { url += `&${key}=${encodeURIComponent(value)}`; } // Load form fields const formContainer = container.querySelector('#igny8-form-container'); if (formContainer) { fetch(url) .then(response => response.text()) .then(html => { formContainer.innerHTML = html; // Re-initialize form handlers for the new content initializePersonalization(); }) .catch(error => { console.error('Error loading inline form:', error); formContainer.innerHTML = '

    Error loading form fields.

    '; }); } } /** * Global function for manual save (called from onclick) */ window.igny8_save_content_manual = function(button) { handleSaveContent(button); }; /** * Initialize Writer AI Settings */ function initializeWriterAISettings() { // Only initialize if we're on the writer home page if (!window.IGNY8_PAGE || window.IGNY8_PAGE.module !== 'writer' || window.IGNY8_PAGE.submodule !== 'home') { return; } // Writer Mode Toggle const writerModeRadios = document.querySelectorAll('input[name="igny8_writer_mode"]'); writerModeRadios.forEach(radio => { radio.addEventListener('change', function() { const aiFeatures = document.getElementById('igny8-writer-ai-features'); if (aiFeatures) { aiFeatures.style.display = this.value === 'ai' ? 'block' : 'none'; } }); }); // Save Writer AI Settings const saveWriterAIBtn = document.getElementById('igny8-save-writer-ai-settings'); if (saveWriterAIBtn) { saveWriterAIBtn.addEventListener('click', function() { saveWriterAISettings(); }); } // Save Content Prompt const saveContentPromptBtn = document.getElementById('igny8-save-content-prompt'); if (saveContentPromptBtn) { saveContentPromptBtn.addEventListener('click', function() { saveContentPrompt(); }); } // Reset Content Prompt const resetContentPromptBtn = document.getElementById('igny8-reset-content-prompt'); if (resetContentPromptBtn) { resetContentPromptBtn.addEventListener('click', function() { resetContentPrompt(); }); } // Save Content Decision button const saveContentDecisionBtn = document.getElementById('igny8-save-content-decision'); console.log('Save Content Decision button found:', saveContentDecisionBtn); if (saveContentDecisionBtn) { console.log('Adding click event listener to Save Content Decision button'); saveContentDecisionBtn.addEventListener('click', function() { console.log('Save Content Decision button clicked!'); saveContentDecision(); }); } else { console.log('Save Content Decision button NOT found!'); } } /** * Save Content Decision */ function saveContentDecision() { console.log('saveContentDecision function called'); const formData = new FormData(); formData.append('action', 'igny8_save_new_content_decision'); formData.append('nonce', window.IGNY8_PAGE.nonce); const newContentAction = document.querySelector('input[name="new_content_action"]:checked')?.value || 'draft'; console.log('Selected content action:', newContentAction); console.log('All radio buttons:', document.querySelectorAll('input[name="new_content_action"]')); formData.append('new_content_action', newContentAction); console.log('Sending AJAX request...'); fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => { console.log('Response received:', response); return response.json(); }) .then(data => { console.log('Response data:', data); if (data.success) { igny8ShowNotification('New content decision saved successfully', 'success'); } else { igny8ShowNotification(data.data?.message || 'Failed to save content decision', 'error'); } }) .catch(error => { console.error('Error saving content decision:', error); igny8ShowNotification('Error saving content decision', 'error'); }); } /** * Save Writer AI Settings */ function saveWriterAISettings() { const formData = new FormData(); formData.append('action', 'igny8_save_writer_ai_settings'); formData.append('nonce', window.IGNY8_PAGE.nonce); const writerMode = document.querySelector('input[name="igny8_writer_mode"]:checked')?.value || 'manual'; const contentGeneration = document.querySelector('input[name="igny8_content_generation"]:checked')?.value || 'enabled'; formData.append('igny8_writer_mode', writerMode); formData.append('igny8_content_generation', contentGeneration); fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { igny8GlobalNotification(data.data.message, 'success'); } else { igny8GlobalNotification(data.data?.message || 'Failed to save Writer AI settings', 'error'); } }) .catch(error => { console.error('Error saving Writer AI settings:', error); igny8GlobalNotification('Error saving Writer AI settings', 'error'); }); } /** * Save Content Generation Prompt */ function saveContentPrompt() { const promptTextarea = document.querySelector('textarea[name="igny8_content_generation_prompt"]'); if (!promptTextarea) return; const formData = new FormData(); formData.append('action', 'igny8_save_content_prompt'); formData.append('nonce', window.IGNY8_PAGE.nonce); formData.append('igny8_content_generation_prompt', promptTextarea.value); fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success) { igny8GlobalNotification(data.data.message, 'success'); } else { igny8GlobalNotification(data.data?.message || 'Failed to save content prompt', 'error'); } }) .catch(error => { console.error('Error saving content prompt:', error); igny8GlobalNotification('Error saving content prompt', 'error'); }); } /** * Reset Content Generation Prompt */ function resetContentPrompt() { if (confirm('Are you sure you want to reset the content generation prompt to default?')) { // This would need to be implemented with a default prompt endpoint igny8GlobalNotification('Reset to default functionality coming soon', 'info'); } } // Personalization initialization moved to main DOMContentLoaded handler // ========================================= // Import/Export Functionality // ========================================= window.initializeImportExport = function() { console.log('Initializing Import/Export functionality...'); // Only initialize if we're on the import-export page if (!window.IGNY8_IMPORT_EXPORT) { console.log('IGNY8_IMPORT_EXPORT not found, skipping initialization'); return; } console.log('IGNY8_IMPORT_EXPORT found:', window.IGNY8_IMPORT_EXPORT); const importForm = document.getElementById('igny8-import-form'); const exportForm = document.getElementById('igny8-export-form'); const settingsForm = document.getElementById('igny8-settings-form'); const downloadTemplateBtns = document.querySelectorAll('.download-template'); console.log('Forms found:', { importForm: !!importForm, exportForm: !!exportForm, settingsForm: !!settingsForm, downloadTemplateBtns: downloadTemplateBtns.length }); // Template download handlers downloadTemplateBtns.forEach(btn => { btn.addEventListener('click', function() { const templateType = this.getAttribute('data-type'); downloadTemplate(templateType); }); }); // Import form handler if (importForm) { importForm.addEventListener('submit', function(e) { if (!runImport()) { e.preventDefault(); } }); } // Export form handler if (exportForm) { exportForm.addEventListener('submit', function(e) { if (!runExport()) { e.preventDefault(); } }); } // Settings form handler if (settingsForm) { settingsForm.addEventListener('submit', function(e) { if (!saveImportExportSettings()) { e.preventDefault(); } }); } }; // Download CSV template function downloadTemplate(templateType) { console.log('Downloading template:', templateType); // Create a form to submit to the AJAX handler const form = document.createElement('form'); form.method = 'POST'; form.action = window.IGNY8_IMPORT_EXPORT.ajaxUrl; form.style.display = 'none'; // Add form fields const actionInput = document.createElement('input'); actionInput.type = 'hidden'; actionInput.name = 'action'; actionInput.value = 'igny8_download_template'; form.appendChild(actionInput); const nonceInput = document.createElement('input'); nonceInput.type = 'hidden'; nonceInput.name = 'nonce'; nonceInput.value = window.IGNY8_IMPORT_EXPORT.nonce; form.appendChild(nonceInput); const tableIdInput = document.createElement('input'); tableIdInput.type = 'hidden'; tableIdInput.name = 'table_id'; tableIdInput.value = 'planner_' + templateType; form.appendChild(tableIdInput); // Add form to document and submit document.body.appendChild(form); form.submit(); document.body.removeChild(form); console.log('Template download initiated'); igny8GlobalNotification('Downloading template...', 'info'); } // Run CSV import function runImport() { console.log('Starting import process...'); const importForm = document.getElementById('igny8-import-form'); const importFile = document.getElementById('import-file'); const importType = document.getElementById('import-type'); const autoCluster = document.getElementById('auto-cluster-import'); const resultsDiv = document.getElementById('import-results'); const submitBtn = importForm.querySelector('button[type="submit"]'); console.log('Import form elements found:', { importForm: !!importForm, importFile: !!importFile, importType: !!importType, autoCluster: !!autoCluster, resultsDiv: !!resultsDiv, submitBtn: !!submitBtn }); if (!importFile.files.length) { console.log('No file selected'); igny8GlobalNotification('Please select a CSV file to import', 'error'); return false; } if (!importType.value) { console.log('No import type selected'); igny8GlobalNotification('Please select an import type', 'error'); return false; } console.log('Import validation passed, submitting form...'); igny8GlobalNotification('Starting import process...', 'info'); // Let the form submit naturally return true; } // Run CSV export function runExport() { console.log('Starting export process...'); const exportForm = document.getElementById('igny8-export-form'); const exportType = document.getElementById('export-type'); const includeMetrics = document.getElementById('include-metrics'); const includeRelationships = document.getElementById('include-relationships'); const includeTimestamps = document.getElementById('include-timestamps'); const submitBtn = exportForm.querySelector('button[type="submit"]'); console.log('Export form elements found:', { exportForm: !!exportForm, exportType: !!exportType, includeMetrics: !!includeMetrics, includeRelationships: !!includeRelationships, includeTimestamps: !!includeTimestamps, submitBtn: !!submitBtn }); if (!exportType.value) { console.log('No export type selected'); igny8GlobalNotification('Please select an export type', 'error'); return false; } console.log('Export validation passed, submitting form...'); igny8GlobalNotification('Starting export process...', 'info'); // Let the form submit naturally return true; } // Save import/export settings function saveImportExportSettings() { console.log('Saving import/export settings...'); igny8GlobalNotification('Saving settings...', 'info'); // Let the form submit naturally return true; } // Display import results function displayImportResults(data, resultsDiv, success = true) { if (!resultsDiv) return; let html = '
    '; html += '

    Import Results

    '; html += `

    Status: ${data.message}

    `; if (data.imported !== undefined) { html += `

    Imported: ${data.imported} records

    `; } if (data.skipped !== undefined) { html += `

    Skipped: ${data.skipped} records

    `; } if (data.details) { html += `

    Details: ${data.details}

    `; } html += '
    '; resultsDiv.innerHTML = html; resultsDiv.style.display = 'block'; } // Display export results function displayExportResults(exportType, success = true) { const resultsDiv = document.getElementById('export-results'); if (!resultsDiv) return; let html = '
    '; html += '

    Export Results

    '; html += `

    Status: ${success ? 'Export completed successfully' : 'Export failed'}

    `; html += `

    Type: ${exportType}

    `; html += `

    File: igny8_export_${exportType}_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.csv

    `; html += '
    '; resultsDiv.innerHTML = html; resultsDiv.style.display = 'block'; } // =================================================================== // IMPORT/EXPORT MODAL FUNCTIONALITY // =================================================================== /** * Show Import Modal * * @param {string} tableId The table ID for configuration */ function igny8ShowImportModal(tableId) { // Remove existing modal if present const existingModal = document.getElementById('igny8-import-export-modal'); if (existingModal) { existingModal.remove(); } // Call PHP function to get modal HTML const formData = new FormData(); formData.append('action', 'igny8_get_import_modal'); formData.append('nonce', window.igny8_ajax?.nonce || ''); formData.append('table_id', tableId); fetch(window.igny8_ajax?.ajax_url || '/wp-admin/admin-ajax.php', { method: 'POST', body: formData }) .then(response => response.json()) .then(result => { if (result.success) { document.body.insertAdjacentHTML('beforeend', result.data); const modal = document.getElementById('igny8-import-export-modal'); // Set the nonce in the form after modal is created const nonceInput = modal.querySelector('input[name="nonce"]'); if (nonceInput && window.igny8_ajax?.nonce) { nonceInput.value = window.igny8_ajax.nonce; } modal.classList.add('open'); } else { igny8ShowNotification('Failed to load import modal', 'error'); } }) .catch(error => { igny8ShowNotification('Error loading import modal', 'error'); }); } /** * Show Export Modal * * @param {string} tableId The table ID for configuration * @param {Array} selectedIds Array of selected row IDs (for export selected) */ function igny8ShowExportModal(tableId, selectedIds = []) { // Remove existing modal if present const existingModal = document.getElementById('igny8-import-export-modal'); if (existingModal) { existingModal.remove(); } // Call PHP function to get modal HTML const formData = new FormData(); formData.append('action', 'igny8_get_export_modal'); formData.append('nonce', window.igny8_ajax?.nonce || ''); formData.append('table_id', tableId); formData.append('selected_ids', JSON.stringify(selectedIds)); fetch(window.igny8_ajax?.ajax_url || '/wp-admin/admin-ajax.php', { method: 'POST', body: formData }) .then(response => response.json()) .then(result => { if (result.success) { document.body.insertAdjacentHTML('beforeend', result.data); const modal = document.getElementById('igny8-import-export-modal'); // Set the nonce in the form after modal is created const nonceInput = modal.querySelector('input[name="nonce"]'); if (nonceInput && window.igny8_ajax?.nonce) { nonceInput.value = window.igny8_ajax.nonce; } modal.classList.add('open'); } else { igny8ShowNotification('Failed to load export modal', 'error'); } }) .catch(error => { igny8ShowNotification('Error loading export modal', 'error'); }); } /** * Close Import/Export Modal */ function igny8CloseImportExportModal() { const modal = document.getElementById('igny8-import-export-modal'); if (modal) { modal.remove(); } } /** * Submit Import Form */ async function igny8SubmitImportForm() { const form = document.getElementById('igny8-modal-import-form'); const fileInput = document.getElementById('import-file'); if (!fileInput.files.length) { igny8ShowNotification('Please select a CSV file', 'error'); return; } const formData = new FormData(form); formData.append('import_file', fileInput.files[0]); // Debug logging console.log('Igny8 Import Debug - Nonce being sent:', window.igny8_ajax?.nonce); console.log('Igny8 Import Debug - AJAX URL:', window.igny8_ajax?.ajax_url); console.log('Igny8 Import Debug - Form data:', Object.fromEntries(formData.entries())); try { const response = await fetch(window.igny8_ajax?.ajax_url || '/wp-admin/admin-ajax.php', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { igny8ShowNotification(result.data.message || 'Import completed successfully', 'success'); igny8CloseImportExportModal(); // Get current table ID from the modal context or page let currentTableId = window.igny8_current_table_id; // If not set globally, try to get from the modal if (!currentTableId) { const modal = document.getElementById('igny8-import-export-modal'); if (modal) { // Try to extract table ID from modal data attributes or other context currentTableId = modal.dataset.tableId || 'planner_keywords'; } else { currentTableId = 'planner_keywords'; // fallback } } // Reload table data if (typeof loadTableData === 'function') { loadTableData(currentTableId, {}, 1); } else if (typeof igny8LoadTableData === 'function') { igny8LoadTableData(currentTableId, {}, 1); } else { // Fallback: reload the page location.reload(); } } else { const errorMessage = typeof result.data === 'object' ? (result.data.message || JSON.stringify(result.data)) : (result.data || 'Import failed'); igny8ShowNotification(errorMessage, 'error'); } } catch (error) { igny8ShowNotification('Import failed due to server error', 'error'); } } /** * Submit Export Form */ async function igny8SubmitExportForm() { const form = document.getElementById('igny8-modal-export-form'); const includeMetrics = document.getElementById('include-metrics')?.checked || false; const includeRelationships = document.getElementById('include-relationships')?.checked || false; const includeTimestamps = document.getElementById('include-timestamps')?.checked || false; const formData = new FormData(form); formData.append('include_metrics', includeMetrics ? '1' : '0'); formData.append('include_relationships', includeRelationships ? '1' : '0'); formData.append('include_timestamps', includeTimestamps ? '1' : '0'); // Debug logging console.log('Igny8 Export Debug - Form data:', Object.fromEntries(formData.entries())); console.log('Igny8 Export Debug - Action:', formData.get('action')); console.log('Igny8 Export Debug - Export type:', formData.get('export_type')); console.log('Igny8 Export Debug - Selected IDs:', formData.get('selected_ids')); try { const response = await fetch(window.igny8_ajax?.ajax_url || '/wp-admin/admin-ajax.php', { method: 'POST', body: formData }); const result = await response.json(); if (result.success) { // Download the CSV file const blob = new Blob([result.data.csv_content], { type: 'text/csv' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = result.data.filename || 'export.csv'; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); igny8ShowNotification(`Exported ${result.data.count} records successfully`, 'success'); igny8CloseImportExportModal(); } else { const errorMessage = typeof result.data === 'object' ? (result.data.message || JSON.stringify(result.data)) : (result.data || 'Export failed'); igny8ShowNotification(errorMessage, 'error'); } } catch (error) { igny8ShowNotification('Export failed due to server error', 'error'); } } /** * Download Template */ function igny8DownloadTemplate(tableId) { // Create form for template download const form = document.createElement('form'); form.method = 'POST'; form.action = window.igny8_ajax?.ajax_url || '/wp-admin/admin-ajax.php'; form.style.display = 'none'; const actionInput = document.createElement('input'); actionInput.type = 'hidden'; actionInput.name = 'action'; actionInput.value = 'igny8_download_template'; form.appendChild(actionInput); const nonceInput = document.createElement('input'); nonceInput.type = 'hidden'; nonceInput.name = 'nonce'; nonceInput.value = window.igny8_ajax?.nonce || ''; form.appendChild(nonceInput); const typeInput = document.createElement('input'); typeInput.type = 'hidden'; typeInput.name = 'table_id'; typeInput.value = tableId; form.appendChild(typeInput); document.body.appendChild(form); form.submit(); document.body.removeChild(form); igny8ShowNotification('Template downloaded', 'success'); } // =================================================================== // PROGRESS MODAL SYSTEM // =================================================================== // Global progress modal instance let currentProgressModal = null; // Show progress modal for AI operations function showProgressModal(title, totalItems, itemType = 'items') { // Remove existing modal if present if (currentProgressModal) { currentProgressModal.remove(); } currentProgressModal = document.createElement('div'); currentProgressModal.id = 'igny8-progress-modal'; currentProgressModal.className = 'igny8-modal'; currentProgressModal.setAttribute('data-item-type', itemType); currentProgressModal.setAttribute('data-total', totalItems); currentProgressModal.innerHTML = `

    ${title}

    Starting...
    Preparing to process ${totalItems} ${itemType}
    0%
    0 Completed
    0 Processing
    ${totalItems} Remaining
    `; document.body.appendChild(currentProgressModal); currentProgressModal.classList.add('open'); return currentProgressModal; } // Update progress modal with live stats function updateProgressModal(current, total, status = 'processing', currentItem = '') { if (!currentProgressModal) return; const itemType = currentProgressModal.getAttribute('data-item-type') || 'items'; const progressText = currentProgressModal.querySelector('#progress-text'); const progressSubtext = currentProgressModal.querySelector('#progress-subtext'); const progressBar = currentProgressModal.querySelector('#progress-bar'); const progressPercentage = currentProgressModal.querySelector('#progress-percentage'); const completedCount = currentProgressModal.querySelector('#completed-count'); const processingCount = currentProgressModal.querySelector('#processing-count'); const remainingCount = currentProgressModal.querySelector('#remaining-count'); const progressIcon = currentProgressModal.querySelector('#progress-icon'); const percentage = Math.round((current / total) * 100); const remaining = Math.max(0, total - current); // Update main text if (progressText) { if (status === 'completed') { progressText.textContent = `✓ Completed ${current} of ${total} ${itemType}`; if (progressIcon) progressIcon.textContent = '✅'; } else { progressText.textContent = `Processing ${current} of ${total} ${itemType}`; } } // Update subtext if (progressSubtext) { if (currentItem) { progressSubtext.textContent = `Current: ${currentItem}`; } else if (status === 'completed') { progressSubtext.textContent = `All ${itemType} processed successfully!`; } else { progressSubtext.textContent = `Working on ${itemType}...`; } } // Update progress bar if (progressBar) { progressBar.style.width = percentage + '%'; } if (progressPercentage) { progressPercentage.textContent = percentage + '%'; } // Update stats if (completedCount) completedCount.textContent = current; if (processingCount) processingCount.textContent = status === 'processing' ? '1' : '0'; if (remainingCount) remainingCount.textContent = remaining; } // Show success modal function showSuccessModal(title, completedCount, message = '') { // Remove progress modal if (currentProgressModal) { currentProgressModal.remove(); currentProgressModal = null; } const modal = document.createElement('div'); modal.id = 'igny8-success-modal'; modal.className = 'igny8-modal'; modal.innerHTML = `

    ${title}

    Done! ${completedCount} items completed.

    ${message ? `

    ${message}

    ` : ''}
    `; document.body.appendChild(modal); modal.classList.add('open'); } // Close success modal function closeSuccessModal() { const modal = document.getElementById('igny8-success-modal'); if (modal) { modal.classList.remove('open'); setTimeout(() => modal.remove(), 300); } } // =================================================================== // SECTOR SELECTION ENFORCEMENT // =================================================================== // Check sector selection before clustering function checkSectorSelectionBeforeClustering(keywordIds) { // Use existing AJAX call to get sector options const formData = new FormData(); formData.append('action', 'igny8_get_saved_sector_selection'); formData.append('nonce', window.IGNY8_PAGE.nonce); fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { if (data.success && data.data && data.data.children && data.data.children.length > 0) { // Sector is selected, proceed with clustering processAIClustering(keywordIds); } else { // No sector selected, show modal using existing modal system showSectorRequiredModal(); } }) .catch(error => { console.error('Error checking sector selection:', error); igny8GlobalNotification('Error checking sector selection', 'error'); }); } // Show sector required modal using existing modal system function showSectorRequiredModal() { const modal = document.createElement('div'); modal.className = 'igny8-modal'; modal.innerHTML = `

    Sector Selection Required

    You must select a Sector before performing Auto Clustering.

    Please go to the Planner dashboard and select your sectors in the "Planner Settings" section.

    `; document.body.appendChild(modal); modal.classList.add('open'); } // Close sector required modal function closeSectorRequiredModal() { const modal = document.querySelector('.igny8-modal'); if (modal) { modal.classList.remove('open'); setTimeout(() => modal.remove(), 300); } } // Go to planner settings function goToPlannerSettings() { closeSectorRequiredModal(); window.location.href = window.location.origin + window.location.pathname + '?page=igny8-planner'; } // =================================================================== // CRON SCHEDULE SETTINGS MODAL // =================================================================== // REMOVED: showCronScheduleModal() - Now handled by Smart Automation System function showCronScheduleModal_DEPRECATED() { const modal = document.createElement('div'); modal.id = 'igny8-cron-schedule-modal'; modal.className = 'igny8-modal'; modal.innerHTML = `

    Cron Schedule Settings

    Use these URLs to trigger automation manually or set up external cron jobs. These URLs use wp-load.php structure and are secured with authentication keys.

    Auto Cluster (Daily) ${getAutomationStatus('auto_cluster_enabled')}
    Loading...
    Auto Generate Ideas (Hourly) ${getAutomationStatus('auto_generate_ideas_enabled')}
    Loading...
    Auto Queue (Hourly) ${getAutomationStatus('auto_queue_enabled')}
    Loading...

    Security Key

    Your security key: ${getSecurityKey()}

    Keep this key secure. It's required to trigger automation externally. The key is automatically generated and stored securely. URL structure: /wp-load.php?import_key=[KEY]&import_id=igny8_cron&action=[ACTION]

    `; document.body.appendChild(modal); modal.classList.add('open'); // Populate URLs after modal is created to ensure key is available setTimeout(() => { const autoClusterUrl = getCronUrl('igny8_auto_cluster_cron'); const autoIdeasUrl = getCronUrl('igny8_auto_generate_ideas_cron'); const autoQueueUrl = getCronUrl('igny8_auto_queue_cron'); document.getElementById('cron-url-auto-cluster').textContent = autoClusterUrl; document.getElementById('cron-url-auto-ideas').textContent = autoIdeasUrl; document.getElementById('cron-url-auto-queue').textContent = autoQueueUrl; // Update copy button onclick handlers with actual URLs const clusterCopyBtn = document.querySelector('#cron-url-auto-cluster').nextElementSibling; const ideasCopyBtn = document.querySelector('#cron-url-auto-ideas').nextElementSibling; const queueCopyBtn = document.querySelector('#cron-url-auto-queue').nextElementSibling; clusterCopyBtn.onclick = () => copyToClipboard(autoClusterUrl); ideasCopyBtn.onclick = () => copyToClipboard(autoIdeasUrl); queueCopyBtn.onclick = () => copyToClipboard(autoQueueUrl); }, 100); } // Close cron schedule modal function closeCronScheduleModal() { const modal = document.getElementById('igny8-cron-schedule-modal'); if (modal) { modal.classList.remove('open'); setTimeout(() => modal.remove(), 300); } } // Get automation status function getAutomationStatus(setting) { const enabled = document.querySelector(`input[name="igny8_${setting}"]`)?.checked; return enabled ? '● Enabled' : '● Disabled'; } // Get cron URL - Updated for wp-load.php endpoint structure (v3.3.0) function getCronUrl(action) { const securityKey = getSecurityKey(); // Return null if no CRON key (page doesn't need CRON functionality) if (!securityKey) { return null; } const baseUrl = window.location.origin; const wpLoadPath = '/wp-load.php'; // Map internal action names to external action names const actionMap = { 'igny8_auto_cluster_cron': 'auto_cluster', 'igny8_auto_generate_ideas_cron': 'auto_ideas', 'igny8_auto_queue_cron': 'auto_queue', 'igny8_auto_drafts_cron': 'auto_content', 'igny8_auto_generate_content_cron': 'auto_content', 'igny8_auto_publish_drafts_cron': 'auto_publish', 'igny8_auto_optimizer_cron': 'auto_optimizer', 'igny8_trigger_recalc': 'auto_recalc' }; const externalAction = actionMap[action] || action; const fullUrl = `${baseUrl}${wpLoadPath}?import_key=${securityKey}&import_id=igny8_cron&action=${externalAction}`; return fullUrl; } // Get security key (retrieve from server) function getSecurityKey() { // Get the secure CRON key from server-side localization const key = window.IGNY8_PAGE?.cronKey; // Return null if no CRON key (page doesn't need CRON functionality) if (!key || key === null) { return null; } return key; } // Copy to clipboard function copyToClipboard(text) { navigator.clipboard.writeText(text).then(() => { igny8GlobalNotification('URL copied to clipboard', 'success'); }).catch(() => { igny8GlobalNotification('Failed to copy URL', 'error'); }); } // Regenerate CRON key function regenerateCronKey() { if (confirm('Are you sure you want to regenerate the CRON key? This will invalidate all existing CRON URLs.')) { // Show loading state const button = event.target.closest('button'); const originalText = button.innerHTML; button.innerHTML = ' Regenerating...'; button.disabled = true; // Make AJAX request to regenerate key fetch(window.IGNY8_PAGE.ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'igny8_regenerate_cron_key', nonce: window.IGNY8_PAGE.nonce }) }) .then(response => response.json()) .then(data => { if (data.success) { // Update the key in the page data window.IGNY8_PAGE.cronKey = data.data.new_key; // Update display elements const keyDisplays = document.querySelectorAll('#cron-key-display, #writer-cron-key-display'); keyDisplays.forEach(display => { display.textContent = data.data.new_key; }); // Update all CRON URLs in the modal const urlElements = document.querySelectorAll('.igny8-cron-url code'); urlElements.forEach(element => { const currentUrl = element.textContent; const newUrl = currentUrl.replace(/import_key=[^&]+/, `import_key=${data.data.new_key}`); element.textContent = newUrl; }); igny8GlobalNotification('CRON key regenerated successfully', 'success'); } else { igny8GlobalNotification('Failed to regenerate CRON key: ' + (data.data?.message || 'Unknown error'), 'error'); } }) .catch(error => { console.error('Error regenerating CRON key:', error); igny8GlobalNotification('Failed to regenerate CRON key', 'error'); }) .finally(() => { // Restore button state button.innerHTML = originalText; button.disabled = false; }); } } // REMOVED: showWriterCronScheduleModal() - Now handled by Smart Automation System function showWriterCronScheduleModal_DEPRECATED() { const modal = document.createElement('div'); modal.id = 'igny8-writer-cron-schedule-modal'; modal.className = 'igny8-modal'; modal.innerHTML = `

    Writer Cron Schedule Settings

    Use these URLs to trigger Writer automation manually or set up external cron jobs. These URLs use wp-load.php structure and are secured with authentication keys.

    Generate Content (Hourly) ${getWriterAutomationStatus('auto_generate_content_enabled')}
    Loading...
    Publish Drafts (Daily) ${getWriterAutomationStatus('auto_publish_drafts_enabled')}
    Loading...

    Security Key

    Your security key: ${getSecurityKey()}

    Keep this key secure. It's required to trigger automation externally. The key is automatically generated and stored securely. URL structure: /wp-load.php?import_key=[KEY]&import_id=igny8_cron&action=[ACTION]

    `; document.body.appendChild(modal); modal.classList.add('open'); // Populate URLs after modal is created to ensure key is available setTimeout(() => { const autoContentUrl = getCronUrl('igny8_auto_generate_content_cron'); const autoPublishUrl = getCronUrl('igny8_auto_publish_drafts_cron'); document.getElementById('writer-cron-url-auto-content').textContent = autoContentUrl; document.getElementById('writer-cron-url-auto-publish').textContent = autoPublishUrl; // Update copy button onclick handlers with actual URLs const contentCopyBtn = document.querySelector('#writer-cron-url-auto-content').nextElementSibling; const publishCopyBtn = document.querySelector('#writer-cron-url-auto-publish').nextElementSibling; contentCopyBtn.onclick = () => copyToClipboard(autoContentUrl); publishCopyBtn.onclick = () => copyToClipboard(autoPublishUrl); }, 100); } // Close Writer cron schedule modal function closeWriterCronScheduleModal() { const modal = document.getElementById('igny8-writer-cron-schedule-modal'); if (modal) { modal.classList.remove('open'); setTimeout(() => modal.remove(), 300); } } // Get Writer automation status function getWriterAutomationStatus(setting) { const enabled = document.querySelector(`input[name="igny8_${setting}"]`)?.checked; return enabled ? '● Enabled' : '● Disabled'; } // =================================================================== // SMART AUTOMATION - RUN NOW AJAX FUNCTIONALITY // =================================================================== /** * Handle Run Now button clicks for cron jobs */ function handleRunNowClick(event) { event.preventDefault(); const button = event.target.closest('button[name="manual_run"]'); if (!button) return; const form = button.closest('form'); const hook = form.querySelector('input[name="hook"]').value; const originalText = button.innerHTML; // Show loading state button.disabled = true; button.innerHTML = ' Running...'; // Make AJAX request jQuery.ajax({ url: ajaxurl, type: 'POST', data: { action: 'igny8_cron_manual_run', hook: hook, nonce: jQuery('#_wpnonce').val() }, success: function(response) { if (response.success) { // Show success message igny8GlobalNotification('Job executed successfully: ' + response.data.message, 'success'); // Refresh the page after a short delay to show updated status setTimeout(function() { location.reload(); }, 1500); } else { igny8GlobalNotification('Error: ' + (response.data || 'Unknown error'), 'error'); button.disabled = false; button.innerHTML = originalText; } }, error: function(xhr, status, error) { igny8GlobalNotification('AJAX Error: ' + error, 'error'); button.disabled = false; button.innerHTML = originalText; } }); } /** * Show notification message */ function igny8GlobalNotification(message, type) { const notification = document.createElement('div'); notification.className = 'notice notice-' + (type === 'success' ? 'success' : 'error') + ' is-dismissible'; notification.style.position = 'fixed'; notification.style.top = '32px'; notification.style.right = '20px'; notification.style.zIndex = '9999'; notification.style.maxWidth = '400px'; notification.innerHTML = '

    ' + message + '

    '; document.body.appendChild(notification); // Auto-dismiss after 5 seconds setTimeout(function() { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }, 5000); // Handle manual dismiss notification.querySelector('.notice-dismiss').addEventListener('click', function() { if (notification.parentNode) { notification.parentNode.removeChild(notification); } }); } // Cron job click handlers moved to main delegated events handler /** * Handle Run Now icon button click */ /** * Handle Open in New Window icon button click */ function handleOpenInNewWindow(button, hook) { // Get the security key from the page let securityKey = ''; // Try to find the security key in various locations on the page const keyInput = document.querySelector('input[name="igny8_secure_cron_key"]'); if (keyInput) { securityKey = keyInput.value; } else { // Try to find it in a hidden field or data attribute const keyElement = document.querySelector('[data-cron-key]'); if (keyElement) { securityKey = keyElement.getAttribute('data-cron-key'); } else { // Try to get it from the page content (if displayed) const keyDisplay = document.querySelector('.igny8-cron-key-display'); if (keyDisplay) { securityKey = keyDisplay.textContent.trim(); } } } // If still no key found, show error if (!securityKey) { igny8GlobalNotification('Security key not found. Please check the cron settings page.', 'error'); return; } // Map hook names to action names for the external URL const actionMap = { 'igny8_auto_cluster_cron': 'auto_cluster', 'igny8_auto_generate_ideas_cron': 'auto_ideas', 'igny8_auto_queue_cron': 'auto_queue', 'igny8_auto_generate_content_cron': 'auto_content', 'igny8_auto_generate_images_cron': 'auto_images', 'igny8_auto_publish_drafts_cron': 'auto_publish', 'igny8_auto_optimizer_cron': 'auto_optimizer', 'igny8_auto_recalc_cron': 'auto_recalc', 'igny8_health_check_cron': 'health_check' }; const action = actionMap[hook] || 'master_scheduler'; const baseUrl = window.location.origin; const cronUrl = baseUrl + '/wp-load.php?import_key=' + encodeURIComponent(securityKey) + '&import_id=igny8_cron&action=' + action; // Open in new window window.open(cronUrl, '_blank', 'width=800,height=600,scrollbars=yes,resizable=yes'); } // Add CSS for spin animation const style = document.createElement('style'); style.textContent = ` @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(style); // =================================================================== // Dynamic Image Size Selector // =================================================================== // Image size options for different providers const imageSizeOptions = { openai: [ { value: '1024x768', label: 'Landscape – 1024 × 768', width: 1024, height: 768 }, { value: '1024x1024', label: 'Square – 1024 × 1024', width: 1024, height: 1024 }, { value: '720x1280', label: 'Social Portrait – 720 × 1280', width: 720, height: 1280 } ], runware: [ { value: '1280x832', label: 'Landscape – 1280 × 832', width: 1280, height: 832 }, { value: '1024x1024', label: 'Square – 1024 × 1024', width: 1024, height: 1024 }, { value: '960x1280', label: 'Social Portrait – 960 × 1280', width: 960, height: 1280 } ] }; // Initialize image size selector when page loads document.addEventListener('DOMContentLoaded', function() { const providerSelect = document.getElementById('image_provider'); const sizeSelect = document.getElementById('igny8_image_size_selector'); const formatSelect = document.getElementById('igny8_image_format_selector'); const dimensionsDisplay = document.getElementById('igny8-selected-dimensions'); const formatDisplay = document.getElementById('igny8-selected-format'); const widthInput = document.getElementById('image_width'); const heightInput = document.getElementById('image_height'); if (providerSelect && sizeSelect && widthInput && heightInput) { // Function to update size options based on provider function updateSizeOptions() { const selectedProvider = providerSelect.value; const options = imageSizeOptions[selectedProvider] || imageSizeOptions.openai; // Clear existing options sizeSelect.innerHTML = ''; // Add new options options.forEach((option, index) => { const optionElement = document.createElement('option'); optionElement.value = option.value; optionElement.textContent = option.label; if (index === 0) optionElement.selected = true; // Select first option by default sizeSelect.appendChild(optionElement); }); // Update dimensions and hidden fields updateDimensions(); } // Function to update dimensions display and hidden fields function updateDimensions() { const selectedSize = sizeSelect.value; const selectedProvider = providerSelect.value; const options = imageSizeOptions[selectedProvider] || imageSizeOptions.openai; const selectedOption = options.find(opt => opt.value === selectedSize); if (selectedOption) { widthInput.value = selectedOption.width; heightInput.value = selectedOption.height; if (dimensionsDisplay) { dimensionsDisplay.textContent = `Selected: ${selectedOption.width} × ${selectedOption.height} (${selectedOption.label.split(' – ')[0]})`; } } } // Function to update format display function updateFormatDisplay() { if (formatSelect && formatDisplay) { const selectedFormat = formatSelect.value.toUpperCase(); formatDisplay.textContent = `Selected format: ${selectedFormat}`; } } // Event listeners providerSelect.addEventListener('change', updateSizeOptions); sizeSelect.addEventListener('change', updateDimensions); if (formatSelect) { formatSelect.addEventListener('change', updateFormatDisplay); } // Initialize on page load updateSizeOptions(); updateFormatDisplay(); } }); // =================================================================== // Test Runware API Connection // =================================================================== // Test Runware API Connection button handler document.addEventListener('click', function(event) { if (event.target && event.target.id === 'igny8-test-runware-btn') { event.preventDefault(); const button = event.target; const resultDiv = document.getElementById('igny8-runware-test-result'); // Disable button and show loading state button.disabled = true; button.textContent = 'Testing...'; // Clear previous results if (resultDiv) { resultDiv.innerHTML = ''; } // Make AJAX request fetch(igny8_ajax.ajax_url, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ action: 'igny8_test_runware_connection', nonce: igny8_ajax.nonce }) }) .then(response => response.json()) .then(data => { // Re-enable button button.disabled = false; button.textContent = 'Test Runware Connection'; // Show result if (resultDiv) { if (data.success) { resultDiv.innerHTML = '

    ' + data.data.message + '

    '; } else { resultDiv.innerHTML = '

    ' + data.data.message + '

    '; } } }) .catch(error => { // Re-enable button button.disabled = false; button.textContent = 'Test Runware Connection'; // Show error if (resultDiv) { resultDiv.innerHTML = '

    ❌ Connection failed: Network error

    '; } console.error('Runware API test error:', error); }); } }); // =================================================================== // DESCRIPTION TOGGLE FUNCTIONALITY // =================================================================== // Handle description toggle clicks document.addEventListener('click', function(e) { const toggleBtn = e.target.closest('.igny8-description-toggle'); if (toggleBtn) { e.preventDefault(); e.stopPropagation(); const rowId = toggleBtn.dataset.rowId; const description = toggleBtn.dataset.description; const tableRow = toggleBtn.closest('tr'); // Check if description row already exists let descriptionRow = document.querySelector(`tr.igny8-description-row[data-parent-id="${rowId}"]`); if (descriptionRow && descriptionRow.classList.contains('expanded')) { // Close existing description row descriptionRow.classList.remove('expanded'); setTimeout(() => { if (!descriptionRow.classList.contains('expanded')) { descriptionRow.remove(); } }, 300); } else { // Remove any existing description rows for this table const existingRows = document.querySelectorAll(`tr.igny8-description-row[data-parent-id="${rowId}"]`); existingRows.forEach(row => row.remove()); // Parse and format description (handle both JSON and plain text) let formattedDescription = ''; try { // Try to parse as JSON first const descriptionData = JSON.parse(description); if (descriptionData && typeof descriptionData === 'object') { formattedDescription = '
    '; // Handle H2 sections if they exist if (descriptionData.H2 && Array.isArray(descriptionData.H2)) { descriptionData.H2.forEach((section, index) => { if (section.heading && section.content_type && section.details) { formattedDescription += `

    ${section.heading}

    ${section.content_type.replace('_', ' ').toUpperCase()}
    ${section.details}
    `; } }); } else { // If it's JSON but not the expected format, show as structured data Object.keys(descriptionData).forEach(key => { if (descriptionData[key]) { const label = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); formattedDescription += `
    ${label}: ${descriptionData[key]}
    `; } }); } formattedDescription += '
    '; } else { formattedDescription = '
    Invalid description format
    '; } } catch (error) { // If JSON parsing fails, treat as plain text formattedDescription = `
    ${description}
    `; } // Create new description row const newRow = document.createElement('tr'); newRow.className = 'igny8-description-row expanded'; newRow.setAttribute('data-parent-id', rowId); const cellCount = tableRow.cells.length; newRow.innerHTML = ` ${formattedDescription} `; // Insert after the current row tableRow.parentNode.insertBefore(newRow, tableRow.nextSibling); } } }); // Handle image prompts toggle clicks document.addEventListener('click', function(e) { const toggleBtn = e.target.closest('.igny8-image-prompts-toggle'); if (toggleBtn) { e.preventDefault(); e.stopPropagation(); const rowId = toggleBtn.dataset.rowId; const imagePrompts = toggleBtn.dataset.imagePrompts; const tableRow = toggleBtn.closest('tr'); // Check if image prompts row already exists let imagePromptsRow = document.querySelector(`tr.igny8-image-prompts-row[data-parent-id="${rowId}"]`); if (imagePromptsRow && imagePromptsRow.classList.contains('expanded')) { // Close existing image prompts row imagePromptsRow.classList.remove('expanded'); setTimeout(() => { if (!imagePromptsRow.classList.contains('expanded')) { imagePromptsRow.remove(); } }, 300); } else { // Remove any existing image prompts rows for this table const existingRows = document.querySelectorAll(`tr.igny8-image-prompts-row[data-parent-id="${rowId}"]`); existingRows.forEach(row => row.remove()); // Parse and format image prompts let formattedPrompts = ''; try { if (!imagePrompts || imagePrompts.trim() === '') { formattedPrompts = '
    No image prompts available
    '; } else { const prompts = JSON.parse(imagePrompts); if (prompts && typeof prompts === 'object') { formattedPrompts = '
    '; const promptKeys = Object.keys(prompts); if (promptKeys.length === 0) { formattedPrompts += '
    No prompts found in data
    '; } else { promptKeys.forEach(key => { if (prompts[key] && prompts[key].trim() !== '') { const label = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); formattedPrompts += `
    ${label}: ${prompts[key]}
    `; } }); } formattedPrompts += '
    '; } else { formattedPrompts = '
    Invalid prompts data format
    '; } } } catch (error) { console.error('Error parsing image prompts:', error); formattedPrompts = '
    Error parsing image prompts: ' + error.message + '
    '; } // Create new image prompts row const newRow = document.createElement('tr'); newRow.className = 'igny8-image-prompts-row expanded'; newRow.setAttribute('data-parent-id', rowId); const cellCount = tableRow.cells.length; newRow.innerHTML = ` ${formattedPrompts} `; // Insert after the current row tableRow.parentNode.insertBefore(newRow, tableRow.nextSibling); } } }); // Handle click outside to close description and image prompts rows document.addEventListener('click', function(e) { // Check if click is outside any description toggle button if (!e.target.closest('.igny8-description-toggle') && !e.target.closest('.igny8-description-row')) { // Close all expanded description rows const expandedRows = document.querySelectorAll('.igny8-description-row.expanded'); expandedRows.forEach(row => { row.classList.remove('expanded'); setTimeout(() => { if (!row.classList.contains('expanded')) { row.remove(); } }, 300); }); } // Check if click is outside any image prompts toggle button if (!e.target.closest('.igny8-image-prompts-toggle') && !e.target.closest('.igny8-image-prompts-row')) { // Close all expanded image prompts rows const expandedImageRows = document.querySelectorAll('.igny8-image-prompts-row.expanded'); expandedImageRows.forEach(row => { row.classList.remove('expanded'); setTimeout(() => { if (!row.classList.contains('expanded')) { row.remove(); } }, 300); }); } }); // =================================================================== // END OF UNIFIED JAVASCRIPT // ===================================================================