Files
igny8/igny8-ai-seo-wp-plugin/assets/js/core.js
2025-11-11 21:16:37 +05:00

4962 lines
187 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 = '<option value="">Choose a parent sector...</option>';
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 = `
<div class="igny8-selected-parent">
<strong>Parent Sector:</strong> ${parent.name}
</div>
<div class="igny8-selected-children">
<strong>Child Sectors:</strong>
<ul>
${children.map(child => `<li>${child.name}</li>`).join('')}
</ul>
</div>
`;
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 = '<span class="dashicons dashicons-update" style="font-size: 14px; animation: spin 1s linear infinite;"></span> 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 = '<span class="dashicons dashicons-yes" style="font-size: 14px; color: var(--green);"></span> 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 = `
<div class="igny8-modal-content">
<div class="igny8-modal-header">
<h3>Keywords in "${clusterName}"</h3>
<button class="igny8-btn-close" onclick="document.getElementById('cluster-keywords-modal').remove()">&times;</button>
</div>
<div class="igny8-modal-body">
${keywords.length > 0 ?
`<ul>${keywords.map(k => `<li>${k.keyword} (${k.search_volume} vol, ${k.difficulty} diff)</li>`).join('')}</ul>` :
'<p>No keywords found in this cluster.</p>'
}
</div>
<div class="igny8-modal-footer">
<button type="button" class="igny8-btn-secondary" onclick="document.getElementById('cluster-keywords-modal').remove()">Close</button>
</div>
</div>
`;
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 = `
<div class="igny8-modal-content">
<div class="igny8-modal-header">
<h3>${headerTitle}</h3>
<button class="igny8-btn-close" onclick="igny8CancelDelete()">&times;</button>
</div>
<div class="igny8-modal-body">${bodyHTML}</div>
<div class="igny8-modal-footer">
<button type="button" class="igny8-btn-secondary" onclick="igny8CancelDelete()">Cancel</button>
<button type="button" class="igny8-btn-danger" onclick="igny8ConfirmDelete()">Delete</button>
</div>
</div>`;
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 `
<p>Are you sure you want to delete this record?</p>
<p><strong>${title}</strong></p>
<p class="igny8-text-danger">This action cannot be undone.</p>`;
}
// ---- 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 => `<li>${r.keyword || r.name || r.idea_title || 'Unknown'}</li>`)
.join('');
const moreText = remaining > 0 ? `<li>... and ${remaining} more records</li>` : '';
return `
<p>Are you sure you want to delete <strong>${total}</strong> records?</p>
<ul>${previewItems}${moreText}</ul>
<p class="igny8-text-danger">This action cannot be undone.</p>`;
}
// ---- 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 = `<td colspan="9" class="igny8-loading-cell">Loading...</td>`;
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 = '<div class="igny8-loading">Loading personalization form...</div>';
// 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 = '<div class="igny8-loading">Generating personalized content...</div>';
}
// Submit form
fetch(ajaxUrl, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
if (outputContainer) {
outputContainer.innerHTML = `
<div class="igny8-personalized-content">
<h3>Your Personalized Content</h3>
<div class="igny8-content-body">
${data.data}
</div>
<div class="igny8-content-actions">
<button type="button" class="button" onclick="igny8SaveContent(${postId}, this)">
Save This Content
</button>
</div>
</div>
`;
}
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 = '<div style="color: red;">Error generating personalized content: ' + error.message + '</div>';
}
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 = '<span class="dashicons dashicons-update" style="animation: spin 1s linear infinite;"></span> 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 = '<p>Error loading form fields.</p>';
});
}
}
/**
* 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 = '<div class="igny8-results-container" style="padding: 15px; border-radius: 4px; margin-top: 15px; ';
html += success ? 'background: #d4edda; border: 1px solid #c3e6cb; color: #155724;' : 'background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24;';
html += '">';
html += '<h4 style="margin: 0 0 10px 0;">Import Results</h4>';
html += `<p style="margin: 5px 0;"><strong>Status:</strong> ${data.message}</p>`;
if (data.imported !== undefined) {
html += `<p style="margin: 5px 0;"><strong>Imported:</strong> ${data.imported} records</p>`;
}
if (data.skipped !== undefined) {
html += `<p style="margin: 5px 0;"><strong>Skipped:</strong> ${data.skipped} records</p>`;
}
if (data.details) {
html += `<p style="margin: 5px 0;"><strong>Details:</strong> ${data.details}</p>`;
}
html += '</div>';
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 = '<div class="igny8-results-container" style="padding: 15px; border-radius: 4px; margin-top: 15px; ';
html += success ? 'background: #d4edda; border: 1px solid #c3e6cb; color: #155724;' : 'background: #f8d7da; border: 1px solid #f5c6cb; color: #721c24;';
html += '">';
html += '<h4 style="margin: 0 0 10px 0;">Export Results</h4>';
html += `<p style="margin: 5px 0;"><strong>Status:</strong> ${success ? 'Export completed successfully' : 'Export failed'}</p>`;
html += `<p style="margin: 5px 0;"><strong>Type:</strong> ${exportType}</p>`;
html += `<p style="margin: 5px 0;"><strong>File:</strong> igny8_export_${exportType}_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.csv</p>`;
html += '</div>';
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 = `
<div class="igny8-modal-content">
<div class="igny8-modal-header">
<h3>${title}</h3>
</div>
<div class="igny8-modal-body" style="text-align: center;">
<div style="font-size: 48px; margin-bottom: 16px; color: var(--blue);" id="progress-icon">⏳</div>
<div id="progress-text" style="margin-bottom: 12px; font-size: 18px; font-weight: 600; color: var(--text);">
Starting...
</div>
<div id="progress-subtext" style="margin-bottom: 20px; font-size: 14px; color: var(--text-light);">
Preparing to process ${totalItems} ${itemType}
</div>
<div style="width: 100%; background: var(--panel-2); border-radius: 10px; height: 24px; overflow: hidden; margin-bottom: 12px; position: relative;">
<div id="progress-bar" style="width: 0%; height: 100%; background: linear-gradient(90deg, var(--blue) 0%, var(--blue-dark) 100%); transition: width 0.5s ease; position: relative;">
<div style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); color: white; font-size: 12px; font-weight: bold; text-shadow: 0 1px 2px rgba(0,0,0,0.3);" id="progress-percentage">0%</div>
</div>
</div>
<div id="progress-stats" style="display: flex; justify-content: space-around; font-size: 13px; color: var(--text-light);">
<div><strong id="completed-count">0</strong> Completed</div>
<div><strong id="processing-count">0</strong> Processing</div>
<div><strong id="remaining-count">${totalItems}</strong> Remaining</div>
</div>
</div>
</div>
`;
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 = `
<div class="igny8-modal-content">
<div class="igny8-modal-header">
<h3>${title}</h3>
</div>
<div class="igny8-modal-body" style="text-align: center;">
<div style="font-size: 48px; margin-bottom: 16px; color: var(--green);">✅</div>
<p style="font-size: 16px; color: var(--text); margin-bottom: 8px;">
<strong>Done! ${completedCount} items completed.</strong>
</p>
${message ? `<p style="font-size: 14px; color: var(--text-dim);">${message}</p>` : ''}
</div>
<div class="igny8-modal-footer">
<button class="igny8-btn igny8-btn-primary" onclick="closeSuccessModal()">OK</button>
</div>
</div>
`;
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 = `
<div class="igny8-modal-content">
<div class="igny8-modal-header">
<h3>Sector Selection Required</h3>
<button onclick="closeSectorRequiredModal()" style="background: none; border: none; font-size: 24px; cursor: pointer;">&times;</button>
</div>
<div class="igny8-modal-body">
<div style="font-size: 48px; margin-bottom: 16px; color: var(--amber);">⚠</div>
<p><strong>You must select a Sector before performing Auto Clustering.</strong></p>
<p>Please go to the Planner dashboard and select your sectors in the "Planner Settings" section.</p>
</div>
<div class="igny8-modal-footer">
<button class="igny8-btn igny8-btn-primary" onclick="goToPlannerSettings()">Go to Planner Settings</button>
<button class="igny8-btn igny8-btn-outline" onclick="closeSectorRequiredModal()">Cancel</button>
</div>
</div>
`;
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 = `
<div class="igny8-modal-content">
<div class="igny8-modal-header">
<h3>Cron Schedule Settings</h3>
<button onclick="closeCronScheduleModal()" style="background: none; border: none; font-size: 24px; cursor: pointer;">&times;</button>
</div>
<div class="igny8-modal-body">
<p style="margin-bottom: 20px; color: var(--text-dim);">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.</p>
<div class="igny8-cron-config">
<div class="igny8-cron-item">
<div class="igny8-cron-header">
<strong>Auto Cluster (Daily)</strong>
<span class="igny8-cron-status">${getAutomationStatus('auto_cluster_enabled')}</span>
</div>
<div class="igny8-cron-url">
<code id="cron-url-auto-cluster">Loading...</code>
<button class="igny8-btn-copy" onclick="copyToClipboard(document.getElementById('cron-url-auto-cluster').textContent)">Copy</button>
</div>
</div>
<div class="igny8-cron-item">
<div class="igny8-cron-header">
<strong>Auto Generate Ideas (Hourly)</strong>
<span class="igny8-cron-status">${getAutomationStatus('auto_generate_ideas_enabled')}</span>
</div>
<div class="igny8-cron-url">
<code id="cron-url-auto-ideas">Loading...</code>
<button class="igny8-btn-copy" onclick="copyToClipboard(document.getElementById('cron-url-auto-ideas').textContent)">Copy</button>
</div>
</div>
<div class="igny8-cron-item">
<div class="igny8-cron-header">
<strong>Auto Queue (Hourly)</strong>
<span class="igny8-cron-status">${getAutomationStatus('auto_queue_enabled')}</span>
</div>
<div class="igny8-cron-url">
<code id="cron-url-auto-queue">Loading...</code>
<button class="igny8-btn-copy" onclick="copyToClipboard(document.getElementById('cron-url-auto-queue').textContent)">Copy</button>
</div>
</div>
</div>
<div class="igny8-cron-info">
<h4>Security Key</h4>
<p>Your security key: <code id="cron-key-display">${getSecurityKey()}</code></p>
<button type="button" class="igny8-btn igny8-btn-outline igny8-btn-sm" onclick="regenerateCronKey()" style="margin-top: 8px;">
<span class="dashicons dashicons-update"></span> Regenerate Key
</button>
<p style="font-size: 12px; color: var(--text-dim); margin-top: 10px;">
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]
</p>
</div>
</div>
<div class="igny8-modal-footer">
<button class="igny8-btn igny8-btn-primary" onclick="closeCronScheduleModal()">Close</button>
</div>
</div>
`;
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 ? '<span style="color: var(--green);">● Enabled</span>' : '<span style="color: var(--text-dim);">● Disabled</span>';
}
// 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 = '<span class="dashicons dashicons-update"></span> 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 = `
<div class="igny8-modal-content">
<div class="igny8-modal-header">
<h3>Writer Cron Schedule Settings</h3>
<button onclick="closeWriterCronScheduleModal()" style="background: none; border: none; font-size: 24px; cursor: pointer;">&times;</button>
</div>
<div class="igny8-modal-body">
<p style="margin-bottom: 20px; color: var(--text-dim);">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.</p>
<div class="igny8-cron-config">
<div class="igny8-cron-item">
<div class="igny8-cron-header">
<strong>Generate Content (Hourly)</strong>
<span class="igny8-cron-status">${getWriterAutomationStatus('auto_generate_content_enabled')}</span>
</div>
<div class="igny8-cron-url">
<code id="writer-cron-url-auto-content">Loading...</code>
<button class="igny8-btn-copy" onclick="copyToClipboard(document.getElementById('writer-cron-url-auto-content').textContent)">Copy</button>
</div>
</div>
<div class="igny8-cron-item">
<div class="igny8-cron-header">
<strong>Publish Drafts (Daily)</strong>
<span class="igny8-cron-status">${getWriterAutomationStatus('auto_publish_drafts_enabled')}</span>
</div>
<div class="igny8-cron-url">
<code id="writer-cron-url-auto-publish">Loading...</code>
<button class="igny8-btn-copy" onclick="copyToClipboard(document.getElementById('writer-cron-url-auto-publish').textContent)">Copy</button>
</div>
</div>
</div>
<div class="igny8-cron-info">
<h4>Security Key</h4>
<p>Your security key: <code id="writer-cron-key-display">${getSecurityKey()}</code></p>
<button type="button" class="igny8-btn igny8-btn-outline igny8-btn-sm" onclick="regenerateCronKey()" style="margin-top: 8px;">
<span class="dashicons dashicons-update"></span> Regenerate Key
</button>
<p style="font-size: 12px; color: var(--text-dim); margin-top: 10px;">
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]
</p>
</div>
</div>
<div class="igny8-modal-footer">
<button class="igny8-btn igny8-btn-primary" onclick="closeWriterCronScheduleModal()">Close</button>
</div>
</div>
`;
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 ? '<span style="color: var(--green);">● Enabled</span>' : '<span style="color: var(--text-dim);">● Disabled</span>';
}
// ===================================================================
// 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 = '<span class="dashicons dashicons-update" style="font-size: 14px; margin-right: 3px; animation: spin 1s linear infinite;"></span> 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 = '<p>' + message + '</p><button type="button" class="notice-dismiss"><span class="screen-reader-text">Dismiss this notice.</span></button>';
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 = '<div class="notice notice-success inline"><p>' + data.data.message + '</p></div>';
} else {
resultDiv.innerHTML = '<div class="notice notice-error inline"><p>' + data.data.message + '</p></div>';
}
}
})
.catch(error => {
// Re-enable button
button.disabled = false;
button.textContent = 'Test Runware Connection';
// Show error
if (resultDiv) {
resultDiv.innerHTML = '<div class="notice notice-error inline"><p>❌ Connection failed: Network error</p></div>';
}
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 = '<div class="igny8-description-content">';
// 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 += `
<div class="description-section">
<h3 class="section-heading">${section.heading}</h3>
<div class="section-content ${section.content_type}">
<div class="content-type-badge">${section.content_type.replace('_', ' ').toUpperCase()}</div>
<div class="content-details">${section.details}</div>
</div>
</div>
`;
}
});
} 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 += `<div class="description-item"><strong>${label}:</strong> ${descriptionData[key]}</div>`;
}
});
}
formattedDescription += '</div>';
} else {
formattedDescription = '<div class="igny8-description-content">Invalid description format</div>';
}
} catch (error) {
// If JSON parsing fails, treat as plain text
formattedDescription = `
<div class="igny8-description-content">
<div class="description-text">${description}</div>
</div>
`;
}
// 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 = `
<td colspan="${cellCount}" class="igny8-description-content-cell">
${formattedDescription}
</td>
`;
// 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 = '<div class="igny8-image-prompts-content">No image prompts available</div>';
} else {
const prompts = JSON.parse(imagePrompts);
if (prompts && typeof prompts === 'object') {
formattedPrompts = '<div class="igny8-image-prompts-content">';
const promptKeys = Object.keys(prompts);
if (promptKeys.length === 0) {
formattedPrompts += '<div class="prompt-item">No prompts found in data</div>';
} else {
promptKeys.forEach(key => {
if (prompts[key] && prompts[key].trim() !== '') {
const label = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
formattedPrompts += `<div class="prompt-item"><strong>${label}:</strong> ${prompts[key]}</div>`;
}
});
}
formattedPrompts += '</div>';
} else {
formattedPrompts = '<div class="igny8-image-prompts-content">Invalid prompts data format</div>';
}
}
} catch (error) {
console.error('Error parsing image prompts:', error);
formattedPrompts = '<div class="igny8-image-prompts-content">Error parsing image prompts: ' + error.message + '</div>';
}
// 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 = `
<td colspan="${cellCount}" class="igny8-image-prompts-content-cell">
${formattedPrompts}
</td>
`;
// 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
// ===================================================================