4962 lines
187 KiB
JavaScript
4962 lines
187 KiB
JavaScript
/**
|
||
* 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()">×</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()">×</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;">×</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;">×</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;">×</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
|
||
// ===================================================================
|