/**
* 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 = 'Choose a parent sector... ';
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:
${children.map(child => `${child.name} `).join('')}
`;
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.length > 0 ?
`
${keywords.map(k => `${k.keyword} (${k.search_volume} vol, ${k.difficulty} diff) `).join('')} ` :
'
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 = `
`;
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?
${previewItems}${moreText}
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}
Save This Content
`;
}
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 = `
⏳
Starting...
Preparing to process ${totalItems} ${itemType}
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 = `
✅
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 = `
⚠
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 = `
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.
Security Key
Your security key: ${getSecurityKey()}
Regenerate Key
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 = `
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.
Security Key
Your security key: ${getSecurityKey()}
Regenerate Key
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 + '
Dismiss this notice. ';
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 = `
`;
}
// 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
// ===================================================================