/**
* MxChat Content Generator
*
* Handles modal generation flow, full-width preview with iframe scaling,
* floating chat panel, sidebar navigation, and settings auto-save.
*
* @package MxChat
* @since 3.1.0
*/
(function($) {
'use strict';
var state = {
postId: null,
previewUrl: null,
editUrl: null,
permalink: null,
progressKey: null,
progressPoll: null,
isGenerating: false,
isEditing: false,
chatMessages: [],
chatOpen: false,
iframeScale: 1,
historyLoaded: false,
historyPage: 1,
historyLoading: false,
phraseTimer: null,
phraseIndex: 0,
pollStartTime: null,
customSystemPrompt: '',
lastProgressTime: null,
postStatus: null
};
var loadingPhrases = [
'Consulting our AI overlords...',
'Teaching pixels to paint...',
'Brewing a fresh pot of creativity...',
'Convincing the robots to cooperate...',
'Warming up the content engines...',
'Negotiating with the algorithm...',
'Sprinkling some digital magic...',
'Asking ChatGPT to hold our beer...',
'Running it through the vibe check...',
'Assembling the word wizards...',
'Translating brain waves to HTML...',
'Polishing every last pixel...',
'Taking a quick coffee break...',
'Man, this is going to be good...',
'Almost there... probably...',
'Generating something awesome...',
'Feeding the hamsters that power our servers...',
'Doing that thing where we look busy...',
'Hold tight, genius at work...',
'Making the internet a little bit cooler...',
'Crafting content so good it should be illegal...',
'Our AI designer just said "trust the process"...'
];
// ─── Sidebar Navigation ────────────────────────────────────────────
function initNavigation() {
$(document).on('click', '.mxch-nav-link[data-target], .mxch-nav-sub-link[data-target]', function(e) {
e.preventDefault();
var target = $(this).data('target');
switchSection(target);
$('.mxch-nav-link, .mxch-nav-sub-link').removeClass('active');
$(this).addClass('active');
});
$(document).on('click', '.mxch-mobile-nav-link[data-target]', function(e) {
e.preventDefault();
var target = $(this).data('target');
switchSection(target);
$('.mxch-mobile-nav-link').removeClass('active');
$(this).addClass('active');
closeMobileMenu();
});
$(document).on('click', '.mxch-mobile-menu-btn', function() {
$('.mxch-mobile-menu, .mxch-mobile-overlay').addClass('open');
});
$(document).on('click', '.mxch-mobile-menu-close, .mxch-mobile-overlay', function() {
closeMobileMenu();
});
}
function switchSection(target) {
$('.mxch-section').removeClass('active');
$('#' + target).addClass('active');
}
function closeMobileMenu() {
$('.mxch-mobile-menu, .mxch-mobile-overlay').removeClass('open');
}
// ─── Inline Form ────────────────────────────────────────────────────
function initInlineForm() {
// "Create New" button in toolbar — resets to inline form
$('#mxch-cg-new-btn').on('click', function() {
resetToForm();
});
// On initial load: form is already visible, hide toolbar and preview-wrap chrome
$('#mxch-cg-new-btn').hide();
$('.mxch-cg-toolbar').addClass('mxch-cg-toolbar-minimal');
$('.mxch-cg-preview-wrap').addClass('mxch-cg-preview-wrap-form');
}
function showInlineForm() {
var $form = $('#mxch-cg-inline-form');
$form.removeClass('mxch-cg-form-collapsing').show();
// Hide preview and loading
$('#mxch-cg-preview-iframe').hide();
$('#mxch-cg-loading-indicator').hide();
$('.mxch-cg-preview-wrap').css('height', '');
// Toolbar: hidden; preview-wrap: transparent
$('.mxch-cg-toolbar').addClass('mxch-cg-toolbar-minimal');
$('.mxch-cg-preview-wrap').addClass('mxch-cg-preview-wrap-form');
$('#mxch-cg-new-btn').hide();
$('.mxch-cg-toolbar-right').hide();
$('#mxch-cg-status-dropdown').hide();
$('#mxch-cg-preview-title').text('Content Generator');
setTimeout(function() { $('#mxch-cg-prompt').focus(); }, 100);
}
function hideInlineForm() {
var $form = $('#mxch-cg-inline-form');
$form.addClass('mxch-cg-form-collapsing');
setTimeout(function() {
$form.hide().removeClass('mxch-cg-form-collapsing');
}, 300);
$('.mxch-cg-toolbar').removeClass('mxch-cg-toolbar-minimal');
$('.mxch-cg-preview-wrap').removeClass('mxch-cg-preview-wrap-form');
}
function resetToForm() {
// Reset state
state.postId = null;
state.previewUrl = null;
state.editUrl = null;
state.permalink = null;
state.postStatus = null;
state.chatMessages = [];
closeChatPanel();
closeStatusDropdown();
resetSeoPanel();
$('#mxch-cg-prompt').val('');
showInlineForm();
}
// ─── Generation Flow ───────────────────────────────────────────────
function initGeneration() {
// Show/hide schedule date picker
$('#mxch-cg-status').on('change', function() {
if ($(this).val() === 'future') {
$('.mxch-cg-schedule-wrap').show();
if (!$('#mxch-cg-schedule').val()) {
var tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(9, 0, 0, 0);
$('#mxch-cg-schedule').val(tomorrow.toISOString().slice(0, 16));
}
} else {
$('.mxch-cg-schedule-wrap').hide();
}
});
// Generate button
$('#mxch-cg-generate-btn').on('click', function() {
if (state.isGenerating) return;
startGeneration();
});
}
// ── Edit Default Prompt Modal ──────────────────────────────
function initPromptModal() {
var $modal = $('#mxch-cg-prompt-modal');
var $editor = $('#mxch-cg-system-prompt-editor');
var $btn = $('#mxch-cg-edit-prompt-btn');
var currentDefault = '';
function fetchPromptData(callback) {
var contentType = $('#mxch-cg-type').val() || 'post';
$.post(mxchatContent.ajaxUrl, {
action: 'mxchat_get_default_prompt',
nonce: mxchatContent.nonce,
content_type: contentType
}, function(response) {
if (response.success) {
currentDefault = response.data.default_prompt;
var saved = response.data.saved_prompt || '';
state.customSystemPrompt = saved;
updateButtonState();
if (callback) callback(currentDefault, saved);
}
});
}
function updateButtonState() {
if (state.customSystemPrompt) {
$btn.addClass('mxch-cg-prompt-modified');
} else {
$btn.removeClass('mxch-cg-prompt-modified');
}
}
function openModal() {
fetchPromptData(function(def, saved) {
$editor.val(saved || def);
$modal.fadeIn(200);
});
}
function closeModal() {
$modal.fadeOut(200);
}
function saveToServer(promptText, callback) {
var contentType = $('#mxch-cg-type').val() || 'post';
$.post(mxchatContent.ajaxUrl, {
action: 'mxchat_save_custom_prompt',
nonce: mxchatContent.nonce,
content_type: contentType,
custom_prompt: promptText
}, function(response) {
if (callback) callback(response.success);
});
}
$btn.on('click', openModal);
$('#mxch-cg-prompt-modal-close, #mxch-cg-prompt-cancel, .mxch-cg-prompt-modal-overlay').on('click', closeModal);
$('#mxch-cg-prompt-save').on('click', function() {
var edited = $editor.val().trim();
var customValue = (edited && edited !== currentDefault) ? edited : '';
state.customSystemPrompt = customValue;
saveToServer(customValue);
updateButtonState();
closeModal();
});
$('#mxch-cg-prompt-reset').on('click', function() {
$editor.val(currentDefault);
state.customSystemPrompt = '';
saveToServer('');
updateButtonState();
});
// Load saved state for initial content type on page load
fetchPromptData();
// When content type changes, load the saved prompt for that type
$('#mxch-cg-type').on('change', function() {
fetchPromptData();
});
}
function startGeneration() {
var prompt = $('#mxch-cg-prompt').val().trim();
if (!prompt) {
showNotice('Please enter a prompt describing the content you want to generate.', 'error');
return;
}
state.isGenerating = true;
// Immediately hide form and show loading indicator
$('#mxch-cg-inline-form').hide().removeClass('mxch-cg-form-collapsing');
$('.mxch-cg-toolbar').removeClass('mxch-cg-toolbar-minimal');
$('.mxch-cg-preview-wrap').removeClass('mxch-cg-preview-wrap-form');
$('#mxch-cg-preview-title').text('Generating...');
$('#mxch-cg-new-btn').hide();
$('.mxch-cg-toolbar-right').hide();
$('#mxch-cg-status-dropdown').hide();
closeStatusDropdown();
closeChatPanel();
showLoadingIndicator();
var data = {
action: 'mxchat_generate_content',
nonce: mxchatContent.nonce,
prompt: prompt,
content_type: $('#mxch-cg-type').val(),
post_status: $('#mxch-cg-status').val(),
schedule_date: $('#mxch-cg-schedule').val() || '',
layout: $('#mxch-cg-layout').val() || 'fullwidth',
title_display: $('#mxch-cg-title-display').val() || 'hide',
template_mode: $('#mxch-cg-template-mode').val() || 'off',
custom_system_prompt: state.customSystemPrompt || ''
};
$.ajax({
url: mxchatContent.ajaxUrl,
type: 'POST',
data: data,
timeout: 60000,
success: function(response) {
if (response.success && response.data.progress_key) {
// Async mode — loading indicator already showing
state.progressKey = response.data.progress_key;
startProgressPoll();
} else if (response.success) {
// Sync fallback — full result returned directly
onGenerationSuccess(response.data);
} else {
onGenerationError(response.data && response.data.message ? response.data.message : 'Generation failed.');
}
},
error: function(xhr, status, error) {
onGenerationError('Request failed: ' + (error || status));
}
});
}
function onGenerationSuccess(data) {
state.isGenerating = false;
state.postId = data.post_id;
state.previewUrl = data.preview_url;
state.editUrl = data.edit_url;
state.permalink = data.permalink;
state.postStatus = data.status;
state.chatMessages = [];
state.historyLoaded = false;
// Ensure form and its chrome are hidden
hideInlineForm();
// Show success state on loading indicator briefly before showing preview
var $loadingIndicator = $('#mxch-cg-loading-indicator');
if ($loadingIndicator.is(':visible')) {
stopPhraseRotation();
$('#mxch-cg-loading-phrase').text('Your content is ready!');
updateLoadingProgress(100, 'Complete!');
$loadingIndicator.addClass('mxch-cg-loading-success');
setTimeout(function() {
hideLoadingIndicator();
finishPreviewLoad(data);
}, 1200);
} else {
// Direct/sync flow — no loading indicator was shown
finishPreviewLoad(data);
}
}
function finishPreviewLoad(data) {
// Update toolbar title
$('#mxch-cg-preview-title').text(data.title || 'Preview');
// Show status dropdown
var statusLabels = { draft: 'Draft', publish: 'Published', future: 'Scheduled' };
var $dropdown = $('#mxch-cg-status-dropdown');
var $badge = $('#mxch-cg-status-badge');
$badge.find('.mxch-cg-status-badge-text').text(statusLabels[data.status] || data.status);
$badge.removeClass('mxch-cg-badge-draft mxch-cg-badge-publish mxch-cg-badge-future')
.addClass('mxch-cg-badge-' + data.status);
$dropdown.show();
closeStatusDropdown();
$('.mxch-cg-status-option').removeClass('mxch-cg-status-active');
$('.mxch-cg-status-option[data-status="' + data.status + '"]').addClass('mxch-cg-status-active');
state.postStatus = data.status;
// Show toolbar actions
$('.mxch-cg-toolbar-right').show();
$('#mxch-cg-view-post').attr('href', data.permalink);
// Show "Create New" button in toolbar
var $newBtn = $('#mxch-cg-new-btn');
$newBtn.find('span').text('Create New');
$newBtn.show();
// Load preview
loadPreview(data.preview_url);
// Store post ID on the chat panel for add-on access
$('#mxch-cg-chat').attr('data-post-id', data.post_id);
// Populate image panel with generated images
populateImagePanel(data.images || []);
// Populate meta panel with SEO data
populateMetaPanel(data);
// Auto-run SEO analysis
resetSeoPanel();
setTimeout(function() { runSeoAnalysis(); }, 500);
// Pre-populate chat
$('#mxch-cg-chat-messages').empty();
addChatMessage('assistant', 'Content generated! Request edits like "change the heading to..." or "make the background blue".');
}
function onGenerationError(message) {
state.isGenerating = false;
var $btn = $('#mxch-cg-generate-btn');
$btn.prop('disabled', false).removeClass('mxch-cg-loading');
var $loadingIndicator = $('#mxch-cg-loading-indicator');
if ($loadingIndicator.is(':visible')) {
// Error while loading indicator is showing (async flow)
stopPhraseRotation();
$loadingIndicator.addClass('mxch-cg-loading-error');
$('#mxch-cg-loading-phrase').text('Oops! Something went wrong.');
updateLoadingProgress(0, message);
$('#mxch-cg-loading-progress-fill').css('background', '#ef4444');
// Add retry and dismiss buttons
if (!$loadingIndicator.find('.mxch-cg-loading-error-actions').length) {
var $actions = $(
'
' +
'
' +
'
' +
'
'
);
$loadingIndicator.append($actions);
$actions.find('#mxch-cg-loading-retry').on('click', function() {
hideLoadingIndicator();
showInlineForm();
});
$actions.find('#mxch-cg-loading-dismiss').on('click', function() {
hideLoadingIndicator();
showInlineForm();
});
}
} else {
// Error while modal is still open (pre-async or sync flow)
updateProgress(0, 'Error: ' + message);
$('#mxch-cg-progress .mxch-cg-progress-fill').css('background', '#ef4444');
setTimeout(function() {
$('#mxch-cg-progress').fadeOut(300);
$('#mxch-cg-progress .mxch-cg-progress-fill').css('background', '');
}, 4000);
}
}
function updateProgress(percent, message) {
$('#mxch-cg-progress .mxch-cg-progress-fill').css('width', percent + '%');
$('#mxch-cg-progress .mxch-cg-progress-text').text(message);
}
function startProgressPoll() {
// Clear any existing poll interval (but preserve progressKey — it was just set)
if (state.progressPoll) {
clearInterval(state.progressPoll);
state.progressPoll = null;
}
state.pollStartTime = Date.now();
state.lastProgressTime = Date.now();
state.progressPoll = setInterval(pollProgress, 2500);
}
function pollProgress() {
if (!state.progressKey) return;
// Activity-based timeout: if no progress update received for 3 minutes, stop.
// This allows long generations (many images + long content) to run as long as
// the backend is still making progress, while still catching truly stalled jobs.
var inactiveMs = Date.now() - (state.lastProgressTime || state.pollStartTime);
if (inactiveMs > 180000) {
stopProgressPoll();
onGenerationError('Generation is taking longer than expected. Check your History tab — the post may have been created.');
return;
}
$.ajax({
url: mxchatContent.ajaxUrl,
type: 'POST',
data: {
action: 'mxchat_content_progress',
nonce: mxchatContent.nonce,
progress_key: state.progressKey
},
timeout: 10000,
success: function(response) {
if (!response.success) return;
var d = response.data;
// Any non-waiting response means the backend is alive — reset inactivity timer
if (d.step && d.step !== 'waiting') {
state.lastProgressTime = Date.now();
}
updateProgress(d.percent || 0, d.message || 'Processing...');
updateLoadingProgress(d.percent || 0, d.message || 'Processing...');
if (d.step === 'done' && d.result) {
stopProgressPoll();
onGenerationSuccess(d.result);
} else if (d.step === 'error') {
stopProgressPoll();
onGenerationError(d.message || 'Generation failed.');
}
},
error: function() {
// Silently retry on poll failure — don't stop polling
}
});
}
function stopProgressPoll() {
if (state.progressPoll) {
clearInterval(state.progressPoll);
state.progressPoll = null;
}
state.progressKey = null;
state.pollStartTime = null;
state.lastProgressTime = null;
}
// ─── Loading Indicator ────────────────────────────────────────────
function showLoadingIndicator() {
$('#mxch-cg-inline-form').hide().removeClass('mxch-cg-form-collapsing');
$('#mxch-cg-preview-iframe').hide();
$('.mxch-cg-preview-wrap').css('height', '');
var $loading = $('#mxch-cg-loading-indicator');
$loading
.removeClass('mxch-cg-loading-error mxch-cg-loading-success')
.show();
// Reset mini progress
$('#mxch-cg-loading-progress-fill').css({ 'width': '0%', 'background': '' });
$('#mxch-cg-loading-progress-text').text('Starting...');
// Remove any leftover error actions
$loading.find('.mxch-cg-loading-error-actions').remove();
startPhraseRotation();
}
function hideLoadingIndicator() {
stopPhraseRotation();
$('#mxch-cg-loading-indicator').hide();
}
function startPhraseRotation() {
stopPhraseRotation();
// Fisher-Yates shuffle for variety
var shuffled = loadingPhrases.slice();
for (var i = shuffled.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = shuffled[i];
shuffled[i] = shuffled[j];
shuffled[j] = temp;
}
state.phraseIndex = 0;
var $phrase = $('#mxch-cg-loading-phrase');
// Show first phrase immediately
$phrase.text(shuffled[0]).removeClass('mxch-cg-phrase-exit mxch-cg-phrase-enter');
state.phraseTimer = setInterval(function() {
state.phraseIndex = (state.phraseIndex + 1) % shuffled.length;
var nextText = shuffled[state.phraseIndex];
// Fade out (slide up)
$phrase.addClass('mxch-cg-phrase-exit');
setTimeout(function() {
// Swap text and prepare enter state (below)
$phrase
.text(nextText)
.removeClass('mxch-cg-phrase-exit')
.addClass('mxch-cg-phrase-enter');
// Force reflow then remove enter class to trigger transition
$phrase[0].offsetHeight;
$phrase.removeClass('mxch-cg-phrase-enter');
}, 400); // matches CSS transition duration
}, 4500);
}
function stopPhraseRotation() {
if (state.phraseTimer) {
clearInterval(state.phraseTimer);
state.phraseTimer = null;
}
}
function updateLoadingProgress(percent, message) {
$('#mxch-cg-loading-progress-fill').css('width', percent + '%');
if (message) {
$('#mxch-cg-loading-progress-text').text(message);
}
}
// ─── Preview ───────────────────────────────────────────────────────
function initPreview() {
// Viewport toggle
$(document).on('click', '.mxch-cg-viewport-btn', function() {
var viewport = $(this).data('viewport');
$('.mxch-cg-viewport-btn').removeClass('active');
$(this).addClass('active');
var $container = $('#mxch-cg-preview-container');
if (viewport === 'mobile') {
$container.addClass('mxch-cg-viewport-mobile');
// Reset iframe to natural size for mobile
$('#mxch-cg-preview-iframe').css({
width: '375px',
transform: 'none'
});
} else {
$container.removeClass('mxch-cg-viewport-mobile');
scaleIframe();
}
});
// Recalculate scale on window resize
$(window).on('resize', function() {
if (!$('#mxch-cg-preview-container').hasClass('mxch-cg-viewport-mobile')) {
scaleIframe();
}
});
}
function scaleIframe() {
var $iframe = $('#mxch-cg-preview-iframe');
if (!$iframe.is(':visible')) return;
var $wrap = $('.mxch-cg-preview-wrap');
var containerWidth = $wrap.innerWidth();
var iframeNativeWidth = 1400;
if (containerWidth < iframeNativeWidth) {
var scale = containerWidth / iframeNativeWidth;
state.iframeScale = scale;
$iframe.css({
width: iframeNativeWidth + 'px',
transform: 'scale(' + scale + ')',
height: (Math.max(700, $(window).height() - 220) / scale) + 'px'
});
// Set container height to match scaled iframe
$wrap.css('height', ($iframe.outerHeight() * scale) + 'px');
} else {
state.iframeScale = 1;
$iframe.css({
width: '100%',
transform: 'none',
height: Math.max(700, $(window).height() - 220) + 'px'
});
$wrap.css('height', '');
}
}
function loadPreview(url) {
var $iframe = $('#mxch-cg-preview-iframe');
var $empty = $('.mxch-cg-preview-empty');
$empty.hide();
$iframe.show();
// Attach load handler BEFORE setting src to avoid race condition
$iframe.off('load.scale').on('load.scale', function() {
scaleIframe();
});
// Add mxchat_preview param so PHP hides admin bar in before render
var separator = url.indexOf('?') !== -1 ? '&' : '?';
$iframe.attr('src', url + separator + 'mxchat_preview=1&_t=' + Date.now());
// Also scale immediately for initial sizing
setTimeout(scaleIframe, 100);
}
function refreshPreview() {
if (state.previewUrl) {
loadPreview(state.previewUrl);
}
}
function showPreviewEmpty() {
showInlineForm();
}
// ─── Chat Panel ────────────────────────────────────────────────────
function initChat() {
// Toggle chat panel
$('#mxch-cg-chat-toggle').on('click', function() {
if (state.chatOpen) {
closeChatPanel();
} else {
openChatPanel();
}
});
// Close chat panel
$('#mxch-cg-chat-close').on('click', function() {
closeChatPanel();
});
// Enable/disable send button + auto-resize textarea
$('#mxch-cg-chat-input').on('input', function() {
var hasText = $(this).val().trim().length > 0;
$('#mxch-cg-chat-send').prop('disabled', !hasText || state.isEditing);
// Auto-resize
this.style.height = 'auto';
this.style.height = this.scrollHeight + 'px';
});
// Send on Enter, Shift+Enter for newline
$('#mxch-cg-chat-input').on('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
if (!$(this).val().trim() || state.isEditing) return;
sendEdit();
}
});
// Send button click
$('#mxch-cg-chat-send').on('click', function() {
if (state.isEditing) return;
sendEdit();
});
}
function openChatPanel() {
state.chatOpen = true;
// Use flex display for two-column layout
$('#mxch-cg-chat').css('display', 'flex');
$('#mxch-cg-chat-input').focus();
scrollChatToBottom();
}
function closeChatPanel() {
state.chatOpen = false;
$('#mxch-cg-chat').hide();
}
function sendEdit() {
var input = $('#mxch-cg-chat-input').val().trim();
if (!input || !state.postId) return;
state.isEditing = true;
$('#mxch-cg-chat-input').val('').css('height', 'auto');
$('#mxch-cg-chat-send').prop('disabled', true);
addChatMessage('user', input);
var $loading = $('');
$('#mxch-cg-chat-messages').append($loading);
scrollChatToBottom();
$.ajax({
url: mxchatContent.ajaxUrl,
type: 'POST',
data: {
action: 'mxchat_content_edit',
nonce: mxchatContent.nonce,
post_id: state.postId,
edit_instruction: input
},
timeout: 120000,
success: function(response) {
$loading.remove();
state.isEditing = false;
if (response.success) {
addChatMessage('assistant', response.data.message || 'Content updated.');
if (response.data.preview_url) {
state.previewUrl = response.data.preview_url;
}
refreshPreview();
if (response.data.title) {
$('#mxch-cg-preview-title').text(response.data.title);
}
if (response.data.meta) {
populateMetaPanel(response.data);
}
if (response.data.images) {
populateImagePanel(response.data.images);
}
} else {
addChatMessage('assistant', 'Error: ' + (response.data && response.data.message ? response.data.message : 'Edit failed.'));
}
},
error: function() {
$loading.remove();
state.isEditing = false;
addChatMessage('assistant', 'Error: Request failed. Please try again.');
}
});
}
function addChatMessage(role, content) {
state.chatMessages.push({ role: role, content: content });
var roleClass = role === 'user' ? 'mxch-cg-chat-user' : 'mxch-cg-chat-assistant';
var $msg = $('' +
'
' + escapeHtml(content) + '
' +
'
');
$('#mxch-cg-chat-messages').append($msg);
scrollChatToBottom();
}
function scrollChatToBottom() {
var el = document.getElementById('mxch-cg-chat-messages');
if (el) el.scrollTop = el.scrollHeight;
}
// ─── Settings Auto-Save ────────────────────────────────────────────
function initSettingsAutoSave() {
// Use event delegation so dynamically-enabled fields (e.g. pro toggles
// unlocked by add-ons after page load) still trigger saves.
$('#content-settings').on('change', '[data-field]', function() {
var $field = $(this);
var field = $field.data('field');
var value;
if ($field.is(':checkbox')) {
value = $field.is(':checked') ? 'on' : 'off';
} else {
value = $field.val();
}
saveContentSetting(field, value, $field);
// Keep seoOptimize prefs in sync without page reload
var seoMap = {
seo_optimize_meta_desc: 'meta_description',
seo_optimize_seo_title: 'seo_title',
seo_optimize_slug: 'slug',
seo_optimize_readability: 'readability',
seo_optimize_internal_links: 'internal_links',
seo_optimize_img_alt: 'img_alt',
seo_optimize_featured_img: 'featured_img'
};
if (seoMap[field] && mxchatContent.seoOptimize) {
mxchatContent.seoOptimize[seoMap[field]] = (value === 'on');
}
});
}
function saveContentSetting(field, value, $field) {
var $label = $field.closest('.mxch-field').find('.mxch-field-label');
if (!$label.length) {
$label = $field.closest('.mxch-field').find('.mxch-toggle-label');
}
// Show saving spinner
if ($label.length) {
$label.removeClass('mxch-saved').addClass('mxch-saving');
}
$.ajax({
url: mxchatContent.ajaxUrl,
type: 'POST',
data: {
action: 'mxchat_save_content_setting',
nonce: mxchatContent.nonce,
field: field,
value: value
},
success: function(response) {
if ($label.length) {
$label.removeClass('mxch-saving');
if (response.success) {
$label.addClass('mxch-saved');
setTimeout(function() {
$label.removeClass('mxch-saved');
}, 1500);
}
}
},
error: function(xhr, status, error) {
if ($label.length) {
$label.removeClass('mxch-saving');
}
if (window.console) {
console.warn('MxChat content setting save failed:', field, status, error);
}
}
});
}
// ─── Image Panel ────────────────────────────────────────────────────
function populateImagePanel(images) {
var $grid = $('#mxch-cg-images-grid');
var $empty = $('#mxch-cg-images-empty');
var isLocked = $('.mxch-cg-images-col').hasClass('mxch-cg-pro-locked');
// Clear any previous images (keep the empty state element)
$grid.find('.mxch-cg-image-thumb').remove();
if (!images || images.length === 0) {
$empty.show();
return;
}
$empty.hide();
$.each(images, function(i, img) {
var $thumb = $(
'' +
'
 + ')
' +
'
' +
'
' +
'
' +
'
' +
(isLocked ? '
' : '') +
'
'
);
$grid.append($thumb);
});
}
function escapeAttr(str) {
return String(str).replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(//g, '>');
}
// ─── SEO Tab ────────────────────────────────────────────────────
var seoState = { analyzed: false, analyzing: false, fixing: false, score: null, checks: null };
function initSeo() {
$('#mxch-seo-analyze').on('click', function() {
if (state.postId && !seoState.analyzing) runSeoAnalysis();
});
$('#mxch-seo-ai-optimize').on('click', function() {
if (state.postId && !seoState.fixing) runAiOptimize();
});
// Per-check AI fix buttons (content editor panel only, not dashboard modal)
$(document).on('click', '.mxch-seo-check-fix:not(.mxch-seod-check-fix-btn)', function(e) {
e.stopPropagation();
var $btn = $(this);
if ($btn.hasClass('mxch-seo-check-fixing') || !state.postId) return;
runSeoFixSingle($btn.data('field'), $btn);
});
// Auto-analyze when switching to SEO tab if content exists
$(document).on('click', '.mxch-cg-left-tab[data-tab="seo"]', function() {
if (state.postId && !seoState.analyzed && !seoState.analyzing) runSeoAnalysis();
});
}
function runSeoAnalysis() {
if (!state.postId) return;
seoState.analyzing = true;
$('#mxch-seo-analyze').addClass('mxch-spinning');
$.post(ajaxurl, {
action: 'mxchat_seo_analyze',
nonce: mxchatContent.nonce,
post_id: state.postId,
}).done(function(res) {
if (res.success) {
seoState.analyzed = true;
seoState.score = res.data.score;
seoState.checks = res.data.checks;
renderSeoResults(res.data);
} else {
renderSeoError(res.data || 'Analysis failed');
}
}).fail(function() {
renderSeoError('Connection error');
}).always(function() {
seoState.analyzing = false;
$('#mxch-seo-analyze').removeClass('mxch-spinning');
});
}
function renderSeoResults(data) {
var score = data.score, checks = data.checks, summary = data.summary;
// Score ring
var offset = 163.36 - (score / 100) * 163.36;
var $ring = $('#mxch-seo-ring');
$ring.css('stroke-dashoffset', offset).removeClass('mxch-seo-good mxch-seo-ok mxch-seo-bad');
if (score >= 80) $ring.addClass('mxch-seo-good');
else if (score >= 50) $ring.addClass('mxch-seo-ok');
else $ring.addClass('mxch-seo-bad');
$('#mxch-seo-score').text(score);
$('#mxch-seo-score-label').text(score >= 80 ? 'Great' : score >= 60 ? 'Good' : score >= 40 ? 'Needs Work' : 'Poor');
var parts = [];
if (summary.pass) parts.push(summary.pass + ' passed');
if (summary.warn) parts.push(summary.warn + ' warnings');
if (summary.fail) parts.push(summary.fail + ' issues');
$('#mxch-seo-score-summary').text(parts.join(' \u00b7 '));
// Checklist
var $list = $('#mxch-seo-checklist').empty();
var icons = {
pass: '',
warn: '',
fail: '',
};
// Checks that require the Advanced Content Editor add-on to fix
var addonChecks = { readability: true, internal_links: true, img_alt: true, featured_img: true };
// Map check key → optimize field name for per-check fix buttons
var fixableMap = { meta_desc: 'meta_description', title_length: 'seo_title', slug: 'slug', readability: 'readability', internal_links: 'internal_links', img_alt: 'img_alt', featured_img: 'featured_img' };
var sparkleIcon = '';
var sorted = Object.keys(checks).sort(function(a, b) {
var o = { fail: 0, warn: 1, pass: 2 };
return (o[checks[a].status] || 2) - (o[checks[b].status] || 2);
});
var last = null;
sorted.forEach(function(key) {
var c = checks[key];
if (last && last !== 'pass' && c.status === 'pass') {
$list.append('');
}
last = c.status;
// Show addon/pro badge for gated checks that aren't passing
var badge = '';
if (addonChecks[key] && c.status !== 'pass' && !mxchatContent.hasAdvancedContent) {
if (mxchatContent.isActivated) {
badge = ' ADD-ON';
} else {
badge = ' PRO';
}
}
// Per-check AI fix button for non-passing, fixable checks
var fixBtn = '';
if (c.status !== 'pass' && fixableMap[key]) {
var canFix = !addonChecks[key] || mxchatContent.hasAdvancedContent;
if (canFix) {
fixBtn = '';
}
}
$list.append(
'' +
'
' + icons[c.status] + '
' +
'
' +
'' + escapeHtml(c.label) + badge + '' +
'' + escapeHtml(c.detail) + '' +
'
' +
fixBtn +
'
'
);
});
$('#mxch-seo-actions').toggle(summary.fail > 0 || summary.warn > 0);
}
function renderSeoError(msg) {
$('#mxch-seo-checklist').html('' + escapeHtml(msg) + '
');
}
function runSeoFixSingle(field, $btn) {
$btn.addClass('mxch-seo-check-fixing').prop('disabled', true);
$.post(ajaxurl, {
action: 'mxchat_seo_suggest',
nonce: mxchatContent.nonce,
post_id: state.postId,
field: field,
}).done(function(res) {
if (res.success) {
if (field === 'meta_description') $('#mxch-cg-meta-description').val(res.data.suggestion).trigger('input');
else if (field === 'seo_title') $('#mxch-cg-meta-title').val(res.data.suggestion);
$('.mxch-cg-left-tab[data-tab="meta"]').addClass('mxch-cg-tab-flash');
setTimeout(function() { $('.mxch-cg-left-tab[data-tab="meta"]').removeClass('mxch-cg-tab-flash'); }, 2000);
}
}).always(function() {
$btn.removeClass('mxch-seo-check-fixing').prop('disabled', false);
runSeoAnalysis();
});
}
function runAiOptimize() {
if (!state.postId || seoState.fixing) return;
seoState.fixing = true;
var $btn = $('#mxch-seo-ai-optimize'), origHtml = $btn.html();
$btn.addClass('mxch-seo-fixing').prop('disabled', true)
.html(' Optimizing\u2026');
var prefs = mxchatContent.seoOptimize || {};
var fields = [];
if (seoState.checks) {
if (prefs.meta_description !== false && seoState.checks.meta_desc && seoState.checks.meta_desc.status !== 'pass') fields.push('meta_description');
if (prefs.seo_title !== false && seoState.checks.title_length && seoState.checks.title_length.status !== 'pass') fields.push('seo_title');
if (prefs.slug !== false && seoState.checks.slug && seoState.checks.slug.status !== 'pass') fields.push('slug');
// Readability, internal links, images require Advanced Content Editor add-on
if (mxchatContent.hasAdvancedContent) {
if (prefs.readability !== false && seoState.checks.readability && seoState.checks.readability.status !== 'pass') fields.push('readability');
if (prefs.internal_links !== false && seoState.checks.internal_links && seoState.checks.internal_links.status !== 'pass') fields.push('internal_links');
if (prefs.img_alt !== false && seoState.checks.img_alt && seoState.checks.img_alt.status !== 'pass') fields.push('img_alt');
if (prefs.featured_img !== false && seoState.checks.featured_img && seoState.checks.featured_img.status !== 'pass') fields.push('featured_img');
}
}
if (!fields.length) fields.push('meta_description');
// Run fields sequentially to avoid race conditions
// (multiple optimizers read/write post_content)
var idx = 0;
function runNext() {
if (idx >= fields.length) {
seoState.fixing = false;
$btn.removeClass('mxch-seo-fixing').prop('disabled', false).html(origHtml);
runSeoAnalysis();
return;
}
var field = fields[idx];
$.post(ajaxurl, {
action: 'mxchat_seo_suggest',
nonce: mxchatContent.nonce,
post_id: state.postId,
field: field,
}).done(function(res) {
if (res.success) {
if (field === 'meta_description') $('#mxch-cg-meta-description').val(res.data.suggestion).trigger('input');
else if (field === 'seo_title') $('#mxch-cg-meta-title').val(res.data.suggestion);
else if (field === 'excerpt') $('#mxch-cg-meta-excerpt').val(res.data.suggestion);
$('.mxch-cg-left-tab[data-tab="meta"]').addClass('mxch-cg-tab-flash');
setTimeout(function() { $('.mxch-cg-left-tab[data-tab="meta"]').removeClass('mxch-cg-tab-flash'); }, 2000);
}
}).always(function() {
idx++;
runNext();
});
}
runNext();
}
function resetSeoPanel() {
seoState = { analyzed: false, analyzing: false, fixing: false, score: null, checks: null };
$('#mxch-seo-score').text('\u2014');
$('#mxch-seo-score-label').text('SEO Score');
$('#mxch-seo-score-summary').text('Generate content to analyze');
$('#mxch-seo-ring').css('stroke-dashoffset', '163.36').removeClass('mxch-seo-good mxch-seo-ok mxch-seo-bad');
$('#mxch-seo-checklist').html(
'' +
'' +
'SEO analysis will appear here after content is generated
'
);
$('#mxch-seo-actions').hide();
}
function escapeHtml(s) {
var d = document.createElement('div');
d.appendChild(document.createTextNode(s));
return d.innerHTML;
}
// ─── Left Column Tabs ──────────────────────────────────────────────
function initLeftTabs() {
$(document).on('click', '.mxch-cg-left-tab', function() {
var tab = $(this).data('tab');
$('.mxch-cg-left-tab').removeClass('active');
$(this).addClass('active');
$('.mxch-cg-left-panel').removeClass('active');
$('#mxch-cg-panel-' + tab).addClass('active');
});
// Character counter for meta description
$(document).on('input', '#mxch-cg-meta-description', updateCharCount);
}
function populateMetaPanel(data) {
$('#mxch-cg-meta-title').val(data.title || '');
if (data.meta) {
$('#mxch-cg-meta-description').val(data.meta.description || '');
$('#mxch-cg-meta-keyword').val(data.meta.keyword || '');
$('#mxch-cg-meta-excerpt').val(data.meta.excerpt || '');
}
updateCharCount();
}
function updateCharCount() {
var len = ($('#mxch-cg-meta-description').val() || '').length;
var $counter = $('.mxch-cg-meta-charcount');
$counter.text(len + ' / 160');
if (len > 160) {
$counter.addClass('mxch-cg-meta-charcount-over');
} else {
$counter.removeClass('mxch-cg-meta-charcount-over');
}
}
// ─── History Tab ──────────────────────────────────────────────────
function initHistory() {
$(document).on('click', '[data-target="content-history"]', function() {
if (!state.historyLoaded) {
loadHistory(1);
}
});
$(document).on('click', '.mxch-cg-history-page-btn[data-page]', function() {
var page = $(this).data('page');
if (page && !state.historyLoading) {
loadHistory(page);
}
});
$(document).on('click', '.mxch-cg-history-edit-btn', function() {
var postId = $(this).data('post-id');
if (postId) {
loadPostForEdit(postId, $(this));
}
});
$(document).on('click', '.mxch-cg-history-delete-btn', function() {
var $btn = $(this);
var postId = $btn.data('post-id');
var $item = $btn.closest('.mxch-cg-history-item');
var title = $item.find('.mxch-cg-history-title').text();
if (!confirm('Move "' + title + '" to trash?')) return;
$btn.prop('disabled', true);
$.ajax({
url: mxchatContent.ajaxUrl,
type: 'POST',
data: {
action: 'mxchat_delete_content',
nonce: mxchatContent.nonce,
post_id: postId
},
success: function(response) {
if (response.success) {
$item.slideUp(200, function() { $(this).remove(); });
} else {
alert(response.data.message || 'Failed to delete.');
$btn.prop('disabled', false);
}
},
error: function() {
alert('Request failed. Please try again.');
$btn.prop('disabled', false);
}
});
});
}
function loadHistory(page) {
state.historyLoading = true;
state.historyPage = page;
var $loading = $('#mxch-cg-history-loading');
var $empty = $('#mxch-cg-history-empty');
var $list = $('#mxch-cg-history-list');
var $pag = $('#mxch-cg-history-pagination');
$loading.show();
$empty.hide();
$list.hide();
$pag.hide();
$.ajax({
url: mxchatContent.ajaxUrl,
type: 'POST',
data: {
action: 'mxchat_content_history',
nonce: mxchatContent.nonce,
page: page
},
success: function(response) {
state.historyLoading = false;
state.historyLoaded = true;
$loading.hide();
if (!response.success || !response.data.items.length) {
$empty.show();
return;
}
renderHistoryList(response.data.items);
renderHistoryPagination(response.data.current_page, response.data.total_pages);
$list.show();
if (response.data.total_pages > 1) {
$pag.show();
}
},
error: function() {
state.historyLoading = false;
$loading.hide();
$empty.show();
}
});
}
function renderHistoryList(items) {
var $list = $('#mxch-cg-history-list');
$list.empty();
var statusLabels = {
draft: 'Draft',
publish: 'Published',
future: 'Scheduled',
pending: 'Pending',
'private': 'Private'
};
$.each(items, function(i, item) {
var thumbHtml = item.thumbnail
? '
'
: '';
var statusClass = 'mxch-cg-badge-' + item.status;
var statusText = statusLabels[item.status] || item.status;
var typeLabel = item.post_type === 'page' ? 'Page' : 'Post';
var $row = $(
'' +
'
' + thumbHtml + '
' +
'
' +
'
' + escapeHtml(item.title) + '
' +
'
' +
'' + escapeHtml(statusText) + '' +
'' + escapeHtml(typeLabel) + '' +
'' + escapeHtml(item.date) + '' +
'
' +
'
' +
'
' +
'
' +
'' +
'' +
'
' +
'
' +
'
' +
'
'
);
$list.append($row);
});
}
function renderHistoryPagination(current, total) {
var $pag = $('#mxch-cg-history-pagination');
$pag.empty();
if (total <= 1) return;
var html = '';
if (current > 1) {
html += '';
}
for (var p = 1; p <= total; p++) {
if (p === current) {
html += '' + p + '';
} else if (p === 1 || p === total || (p >= current - 1 && p <= current + 1)) {
html += '';
} else if (p === current - 2 || p === current + 2) {
html += '…';
}
}
if (current < total) {
html += '';
}
$pag.html(html);
}
function loadPostForEdit(postId, $btn) {
var editBtnHtml = ' Edit';
$btn.prop('disabled', true).text('Loading...');
$.ajax({
url: mxchatContent.ajaxUrl,
type: 'POST',
data: {
action: 'mxchat_load_post_for_edit',
nonce: mxchatContent.nonce,
post_id: postId
},
success: function(response) {
$btn.prop('disabled', false).html(editBtnHtml);
if (response.success) {
// Switch to Generate tab
switchSection('content-generate');
$('.mxch-nav-link, .mxch-nav-sub-link').removeClass('active');
$('[data-target="content-generate"]').addClass('active');
$('.mxch-mobile-nav-link').removeClass('active');
$('.mxch-mobile-nav-link[data-target="content-generate"]').addClass('active');
// Load post into the same editor state as fresh generation
onGenerationSuccess(response.data);
} else {
alert(response.data && response.data.message ? response.data.message : 'Failed to load post.');
}
},
error: function() {
$btn.prop('disabled', false).html(editBtnHtml);
alert('Request failed. Please try again.');
}
});
}
// ─── Status Dropdown ──────────────────────────────────────────────
function initStatusDropdown() {
// Toggle dropdown on badge click
$(document).on('click', '#mxch-cg-status-badge', function(e) {
e.stopPropagation();
var $dropdown = $('#mxch-cg-status-dropdown');
if ($dropdown.hasClass('mxch-cg-dropdown-open')) {
closeStatusDropdown();
} else {
openStatusDropdown();
}
});
// Close on outside click
$(document).on('click', function(e) {
if (!$(e.target).closest('#mxch-cg-status-dropdown').length) {
closeStatusDropdown();
}
});
// Close on Escape
$(document).on('keydown', function(e) {
if (e.key === 'Escape') {
closeStatusDropdown();
}
});
// Draft / Publish — immediate status change
$(document).on('click', '.mxch-cg-status-option[data-status="draft"], .mxch-cg-status-option[data-status="publish"]', function() {
var newStatus = $(this).data('status');
if (newStatus === state.postStatus) {
closeStatusDropdown();
return;
}
updatePostStatus(newStatus, '');
});
// Scheduled — show datetime picker
$(document).on('click', '.mxch-cg-status-option[data-status="future"]', function() {
var $scheduleRow = $('.mxch-cg-status-schedule-row');
if ($scheduleRow.is(':visible')) {
$scheduleRow.hide();
return;
}
// Pre-fill with tomorrow at 9am if empty
var $input = $('#mxch-cg-status-schedule-input');
if (!$input.val()) {
var tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(9, 0, 0, 0);
$input.val(tomorrow.toISOString().slice(0, 16));
}
$scheduleRow.show();
$input.focus();
// Highlight scheduled option
$('.mxch-cg-status-option').removeClass('mxch-cg-status-active');
$(this).addClass('mxch-cg-status-active');
});
// Confirm schedule
$(document).on('click', '#mxch-cg-status-schedule-confirm', function() {
var scheduleDate = $('#mxch-cg-status-schedule-input').val();
if (!scheduleDate) {
$('#mxch-cg-status-schedule-input').focus();
return;
}
// Convert datetime-local value to WordPress format (Y-m-d H:i:s)
var wpDate = scheduleDate.replace('T', ' ') + ':00';
updatePostStatus('future', wpDate);
});
// Enter key on datetime input confirms
$(document).on('keydown', '#mxch-cg-status-schedule-input', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
$('#mxch-cg-status-schedule-confirm').trigger('click');
}
});
}
function openStatusDropdown() {
var $dropdown = $('#mxch-cg-status-dropdown');
$dropdown.addClass('mxch-cg-dropdown-open');
$('.mxch-cg-status-menu').show();
// Highlight current status
$('.mxch-cg-status-option').removeClass('mxch-cg-status-active');
$('.mxch-cg-status-option[data-status="' + state.postStatus + '"]').addClass('mxch-cg-status-active');
// Hide schedule row unless current status is future
if (state.postStatus !== 'future') {
$('.mxch-cg-status-schedule-row').hide();
}
}
function closeStatusDropdown() {
$('#mxch-cg-status-dropdown').removeClass('mxch-cg-dropdown-open');
$('.mxch-cg-status-menu').hide();
$('.mxch-cg-status-schedule-row').hide();
}
function updatePostStatus(newStatus, scheduleDate) {
var $badge = $('#mxch-cg-status-badge');
$badge.addClass('mxch-cg-status-updating');
closeStatusDropdown();
$.ajax({
url: mxchatContent.ajaxUrl,
type: 'POST',
data: {
action: 'mxchat_update_post_status',
nonce: mxchatContent.nonce,
post_id: state.postId,
new_status: newStatus,
schedule_date: scheduleDate || ''
},
success: function(response) {
$badge.removeClass('mxch-cg-status-updating');
if (response.success) {
var confirmedStatus = response.data.status;
state.postStatus = confirmedStatus;
// Update badge appearance
var statusLabels = { draft: 'Draft', publish: 'Published', future: 'Scheduled' };
$badge.find('.mxch-cg-status-badge-text').text(statusLabels[confirmedStatus] || confirmedStatus);
$badge.removeClass('mxch-cg-badge-draft mxch-cg-badge-publish mxch-cg-badge-future')
.addClass('mxch-cg-badge-' + confirmedStatus);
// Mark history as stale so it reloads on next visit
state.historyLoaded = false;
// Refresh preview (URL may differ between draft/published)
refreshPreview();
} else {
alert(response.data && response.data.message ? response.data.message : 'Failed to update status.');
}
},
error: function() {
$badge.removeClass('mxch-cg-status-updating');
alert('Request failed. Please try again.');
}
});
}
// ─── Utilities ─────────────────────────────────────────────────────
function escapeHtml(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
// ─── SEO Dashboard (Site-wide) ─────────────────────────────────────
var seodState = {
loaded: false,
loading: false,
page: 1,
pages: 1,
total: 0,
filter: 'all',
postType: 'any',
search: '',
searchTimer: null,
scanning: false,
expandedId: null,
expandAnalyzing: false,
sortBy: 'date',
sortOrder: 'DESC',
};
function initSeoSection() {
// Lazy-load: fetch posts when user first visits SEO section
$(document).on('click', '.mxch-nav-link[data-target="content-seo"], .mxch-nav-sub-link[data-target="content-seo"], .mxch-mobile-nav-link[data-target="content-seo"]', function() {
if (!seodState.loaded && !seodState.loading) {
loadSeoPosts();
}
});
// Filter pills
$(document).on('click', '.mxch-seod-pill', function() {
$('.mxch-seod-pill').removeClass('active');
$(this).addClass('active');
seodState.filter = $(this).data('filter');
seodState.page = 1;
loadSeoPosts();
});
// Post type dropdown
$(document).on('change', '#mxch-seod-post-type', function() {
seodState.postType = $(this).val();
seodState.page = 1;
loadSeoPosts();
});
// Search with debounce
$(document).on('input', '#mxch-seod-search', function() {
var val = $(this).val();
clearTimeout(seodState.searchTimer);
seodState.searchTimer = setTimeout(function() {
seodState.search = val;
seodState.page = 1;
loadSeoPosts();
}, 400);
});
// Pagination
$(document).on('click', '.mxch-seod-page-btn', function() {
var p = $(this).data('page');
if (p && p !== seodState.page) {
seodState.page = p;
loadSeoPosts();
}
});
// Open detail modal on row click
$(document).on('click', '.mxch-seod-row', function() {
var postId = $(this).data('post-id');
openSeoModal(postId);
});
// Close modal
$(document).on('click', '.mxch-seod-modal-overlay', function(e) {
if ($(e.target).hasClass('mxch-seod-modal-overlay')) closeSeoModal();
});
$(document).on('click', '.mxch-seod-modal-close', function() {
closeSeoModal();
});
$(document).on('keydown', function(e) {
if (e.key === 'Escape' && seodState.expandedId) closeSeoModal();
});
// Scan Unscored button
$(document).on('click', '#mxch-seod-scan-all', function() {
if (!seodState.scanning) bulkSeoScan();
});
// Stop scan button
$(document).on('click', '#mxch-seod-scan-stop', function() {
seodState.scanAborted = true;
$(this).prop('disabled', true).find('span').text('Stopping...');
});
// Sortable column headers (all columns sort server-side)
$(document).on('click', '.mxch-seod-header-cell[data-sort]', function() {
var col = $(this).data('sort');
if (seodState.sortBy === col) {
seodState.sortOrder = seodState.sortOrder === 'DESC' ? 'ASC' : 'DESC';
} else {
seodState.sortBy = col;
seodState.sortOrder = col === 'title' ? 'ASC' : 'DESC';
}
seodState.page = 1;
loadSeoPosts();
});
// AI Optimize within detail modal
$(document).on('click', '.mxch-seod-optimize-btn', function(e) {
e.stopPropagation();
var postId = $(this).closest('.mxch-seod-detail').data('post-id');
runSeodOptimize(postId, $(this));
});
// Per-check AI fix buttons in detail modal
$(document).on('click', '.mxch-seod-check-fix-btn', function(e) {
e.stopPropagation();
var $btn = $(this);
if ($btn.hasClass('mxch-seo-check-fixing')) return;
var field = $btn.data('field');
var postId = $btn.data('post-id');
$btn.addClass('mxch-seo-check-fixing').prop('disabled', true);
$.post(ajaxurl, {
action: 'mxchat_seo_suggest',
nonce: mxchatContent.nonce,
post_id: postId,
field: field,
}).always(function() {
$btn.removeClass('mxch-seo-check-fixing').prop('disabled', false);
if (seodState.expandedId === postId) {
openSeoModal(postId);
}
});
});
// Checkbox: prevent row click when clicking checkbox
$(document).on('click', '.mxch-seod-cell-check, .mxch-seod-header-check', function(e) {
e.stopPropagation();
});
// Select all checkbox
$(document).on('change', '.mxch-seod-check-all', function() {
var checked = $(this).prop('checked');
$('.mxch-seod-row-check').prop('checked', checked);
updateOptimizeSelectedBtn();
});
// Individual row checkbox
$(document).on('change', '.mxch-seod-row-check', function() {
var allChecked = $('.mxch-seod-row-check').length === $('.mxch-seod-row-check:checked').length;
$('.mxch-seod-check-all').prop('checked', allChecked);
updateOptimizeSelectedBtn();
});
}
function updateOptimizeSelectedBtn() {
var count = $('.mxch-seod-row-check:checked').length;
var $btn = $('#mxch-seod-optimize-selected');
var $note = $('#mxch-seod-bulk-note');
var isLocked = $btn.hasClass('mxch-seod-bulk-locked');
if (count > 0) {
$btn.find('span').first().text(isLocked ? 'Bulk Optimize' : 'Optimize Selected (' + count + ')');
$btn.show();
if (isLocked) $note.show();
} else {
$btn.hide();
$note.hide();
}
}
function loadSeoPosts() {
seodState.loading = true;
$('#mxch-seod-loading').show();
$('#mxch-seod-empty').hide();
$('#mxch-seod-table').empty();
$.post(ajaxurl, {
action: 'mxchat_seo_list_posts',
nonce: mxchatContent.nonce,
page: seodState.page,
post_type: seodState.postType,
filter: seodState.filter,
search: seodState.search,
sort_by: seodState.sortBy,
sort_order: seodState.sortOrder,
}).done(function(res) {
if (res.success) {
seodState.loaded = true;
seodState.page = res.data.page;
seodState.pages = res.data.pages;
seodState.total = res.data.total;
renderSeodTable(res.data.posts);
renderSeodPagination();
updateSeodScanBtn(res.data.unscored_count);
$('#mxch-seod-footer').show();
if (!res.data.posts.length) {
$('#mxch-seod-empty').show();
}
}
}).fail(function() {
$('#mxch-seod-table').html('Failed to load posts. Please try again.
');
}).always(function() {
seodState.loading = false;
$('#mxch-seod-loading').hide();
});
}
function seodFormatNum(n) {
if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
return n;
}
function seodSortArrow(col) {
if (seodState.sortBy !== col) return '';
return ' ' + (seodState.sortOrder === 'ASC' ? '▲' : '▼') + '';
}
function renderSeodTable(posts) {
var $table = $('#mxch-seod-table');
$table.empty();
seodState.expandedId = null;
// Column headers
var activeClass = function(col) { return seodState.sortBy === col ? ' mxch-seod-header-active' : ''; };
$table.append(
''
);
posts.forEach(function(p) {
var scoreHtml;
if (p.score !== null) {
var cls = p.score >= 80 ? 'mxch-seod-good' : p.score >= 50 ? 'mxch-seod-ok' : 'mxch-seod-bad';
scoreHtml = '' + p.score + '
';
} else {
scoreHtml = '—
';
}
var typeLabel = p.type.charAt(0).toUpperCase() + p.type.slice(1);
$table.append(
'' +
'
' +
'
' +
'
' +
'' + escapeHtml(p.title) + '' +
'' + typeLabel + '' +
'
' +
'
' +
'' + escapeHtml(p.date) + '' +
'
' +
'
' + scoreHtml + '
' +
'
' + (!mxchatContent.hasGSC ? '—' : (p.clicks !== null ? p.clicks : '—')) + '
' +
'
' + (!mxchatContent.hasGSC ? '—' : (p.impressions !== null ? seodFormatNum(p.impressions) : '—')) + '
' +
'
' +
'
'
);
});
}
function renderSeodPagination() {
var $pag = $('#mxch-seod-pagination');
$pag.empty();
if (seodState.pages <= 1) return;
var p = seodState.page, total = seodState.pages;
if (p > 1) {
$pag.append('');
}
$pag.append('Page ' + p + ' of ' + total + '');
if (p < total) {
$pag.append('');
}
}
function updateSeodScanBtn(unscoredCount) {
var $btn = $('#mxch-seod-scan-all');
if (unscoredCount > 0) {
$btn.show().text('Scan Unscored (' + unscoredCount + ')');
} else {
$btn.hide();
}
$('#mxch-seod-scan-status').text('');
}
function openSeoModal(postId) {
closeSeoModal(); // close any existing modal
seodState.expandedId = postId;
var $row = $('.mxch-seod-row[data-post-id="' + postId + '"]');
var title = $row.find('.mxch-seod-title').text() || 'Post #' + postId;
var permalink = $row.attr('data-permalink') || '';
var $overlay = $(
'' +
'
' +
'' +
'
' +
'
' +
'
' +
'
' +
'
Analyzing…' +
'
' +
'
' +
'
' +
'
' +
'
'
);
$('body').append($overlay);
// Trigger reflow then add visible class for animation
$overlay[0].offsetHeight;
$overlay.addClass('mxch-seod-modal-visible');
var $detail = $overlay.find('.mxch-seod-detail');
// Run analysis
seodState.expandAnalyzing = true;
$.post(ajaxurl, {
action: 'mxchat_seo_analyze',
nonce: mxchatContent.nonce,
post_id: postId,
}).done(function(res) {
if (res.success) {
renderSeodDetail($detail, res.data, postId);
// Update the row's score badge in the table too
var score = res.data.score;
var cls = score >= 80 ? 'mxch-seod-good' : score >= 50 ? 'mxch-seod-ok' : 'mxch-seod-bad';
$row.find('.mxch-seod-score-badge')
.removeClass('mxch-seod-good mxch-seod-ok mxch-seod-bad mxch-seod-unscored')
.addClass(cls).text(score);
} else {
$detail.html('Analysis failed. Please try again.
');
}
}).fail(function() {
$detail.html('Connection error. Please try again.
');
}).always(function() {
seodState.expandAnalyzing = false;
});
}
function renderSeodDetail($detail, data, postId) {
var checks = data.checks, score = data.score, summary = data.summary;
var icons = {
pass: '',
warn: '',
fail: '',
};
// Checks that require the Advanced Content Editor add-on to fix
var addonChecks = { readability: true, internal_links: true, img_alt: true, featured_img: true };
var fixableMap = { meta_desc: 'meta_description', title_length: 'seo_title', slug: 'slug', readability: 'readability', internal_links: 'internal_links', img_alt: 'img_alt', featured_img: 'featured_img' };
var sparkleIcon = '';
var sorted = Object.keys(checks).sort(function(a, b) {
var o = { fail: 0, warn: 1, pass: 2 };
return (o[checks[a].status] || 2) - (o[checks[b].status] || 2);
});
var html = '';
var last = null;
sorted.forEach(function(key) {
var c = checks[key];
if (last && last !== 'pass' && c.status === 'pass') {
html += '
';
}
last = c.status;
// Show addon/pro badge for gated checks that aren't passing
var badge = '';
if (addonChecks[key] && c.status !== 'pass' && !mxchatContent.hasAdvancedContent) {
if (mxchatContent.isActivated) {
badge = '
ADD-ON';
} else {
badge = '
PRO';
}
}
// Per-check AI fix button
var fixBtn = '';
if (c.status !== 'pass' && fixableMap[key]) {
var canFix = !addonChecks[key] || mxchatContent.hasAdvancedContent;
if (canFix) {
fixBtn = '
';
}
}
html += '
' +
'
' + icons[c.status] + '
' +
'
' +
'' + escapeHtml(c.label) + badge + '' +
'' + escapeHtml(c.detail) + '' +
'
' +
fixBtn +
'
';
});
html += '
';
// Optimize All button (only if there are issues)
if (summary.fail > 0 || summary.warn > 0) {
html += '' +
'
' +
'
';
}
$detail.html(html);
// GSC placeholder for free/non-addon users
if (!mxchatContent.hasGSC) {
var badgeLabel = mxchatContent.isActivated ? 'ADD-ON' : 'PRO';
var badgeClass = mxchatContent.isActivated ? '' : ' mxch-seod-pro-badge';
var upgradeText = mxchatContent.isActivated ? 'Install Add-on' : 'Upgrade to Pro';
var gscHtml =
'' +
'
' +
'' +
' Search Performance' +
'
' +
'
' +
'
' +
'
42Clicks
' +
'
1.2KImpressions
' +
'
3.5%CTR
' +
'
8.2Avg Position
' +
'
' +
'
' +
'| Keyword | Clicks | Impr. | Position |
' +
'' +
'| example keyword one | 18 | 420 | 5.3 |
' +
'| sample search term | 14 | 380 | 7.1 |
' +
'| another query phrase | 10 | 290 | 12.4 |
' +
'' +
'
' +
'
' +
'
' +
'
';
$detail.append(gscHtml);
}
}
function closeSeoModal() {
seodState.expandedId = null;
var $overlay = $('.mxch-seod-modal-overlay');
if ($overlay.length) {
$overlay.removeClass('mxch-seod-modal-visible');
setTimeout(function() { $overlay.remove(); }, 200);
}
}
function bulkSeoScan() {
seodState.scanning = true;
seodState.scanAborted = false;
var $btn = $('#mxch-seod-scan-all');
var $status = $('#mxch-seod-scan-status');
$btn.hide();
// Show stop button
if (!$('#mxch-seod-scan-stop').length) {
$btn.after('');
}
$('#mxch-seod-scan-stop').show();
$status.text('Loading unscored posts...');
// Fetch ALL unscored post IDs across all pages
var allIds = [];
function fetchPage(page) {
$.post(ajaxurl, {
action: 'mxchat_seo_list_posts',
nonce: mxchatContent.nonce,
page: page,
post_type: 'any',
filter: 'unscored',
search: '',
}).done(function(res) {
if (!res.success || !res.data.posts.length) {
if (allIds.length === 0) {
finishScan('All posts have been scanned.');
return;
}
startScanning(allIds);
return;
}
res.data.posts.forEach(function(p) { allIds.push(p.id); });
if (page < res.data.pages) {
$status.text('Loading unscored posts... (' + allIds.length + ' found)');
fetchPage(page + 1);
} else {
startScanning(allIds);
}
}).fail(function() {
finishScan('Error loading posts.');
});
}
function startScanning(ids) {
var total = ids.length;
var scanned = 0;
var batchSize = 10;
$status.html('0 / ' + total + '');
function updateRows(results) {
$.each(results, function(pid, data) {
var $row = $('.mxch-seod-row[data-post-id="' + pid + '"]');
if ($row.length) {
var score = data.score;
var cls = score >= 80 ? 'mxch-seod-good' : score >= 50 ? 'mxch-seod-ok' : 'mxch-seod-bad';
$row.find('.mxch-seod-score-badge')
.removeClass('mxch-seod-unscored').addClass(cls).text(score);
}
});
}
function scanNextBatch() {
if (seodState.scanAborted) {
finishScan('Stopped — ' + scanned + ' of ' + total + ' scanned.');
loadSeoPosts();
return;
}
if (scanned >= total) {
finishScan('Done! ' + total + ' posts scanned.');
loadSeoPosts();
return;
}
var batch = ids.slice(scanned, scanned + batchSize);
$status.html('' + (scanned + 1) + ' / ' + total + '');
$.post(ajaxurl, {
action: 'mxchat_seo_analyze_batch',
nonce: mxchatContent.nonce,
'post_ids[]': batch,
}).done(function(res) {
if (res.success && res.data.results) {
updateRows(res.data.results);
}
}).always(function() {
scanned += batch.length;
$status.html('' + scanned + ' / ' + total + '');
scanNextBatch();
});
}
scanNextBatch();
}
function finishScan(msg) {
seodState.scanning = false;
seodState.scanAborted = false;
$('#mxch-seod-scan-stop').hide();
$btn.show().prop('disabled', false);
$status.text(msg);
}
fetchPage(1);
}
function runSeodOptimize(postId, $btn) {
var origHtml = $btn.html();
$btn.prop('disabled', true).html(
'' +
' Optimizing…'
);
// Get the current checks to find what needs fixing
$.post(ajaxurl, {
action: 'mxchat_seo_analyze',
nonce: mxchatContent.nonce,
post_id: postId,
}).done(function(res) {
if (!res.success) {
$btn.prop('disabled', false).html(origHtml);
return;
}
var checks = res.data.checks;
var prefs = mxchatContent.seoOptimize || {};
var fields = [];
if (prefs.meta_description !== false && checks.meta_desc && checks.meta_desc.status !== 'pass') fields.push('meta_description');
if (prefs.seo_title !== false && checks.title_length && checks.title_length.status !== 'pass') fields.push('seo_title');
if (prefs.slug !== false && checks.slug && checks.slug.status !== 'pass') fields.push('slug');
// Readability, internal links, images require Advanced Content Editor add-on
if (mxchatContent.hasAdvancedContent) {
if (prefs.readability !== false && checks.readability && checks.readability.status !== 'pass') fields.push('readability');
if (prefs.internal_links !== false && checks.internal_links && checks.internal_links.status !== 'pass') fields.push('internal_links');
if (prefs.img_alt !== false && checks.img_alt && checks.img_alt.status !== 'pass') fields.push('img_alt');
if (prefs.featured_img !== false && checks.featured_img && checks.featured_img.status !== 'pass') fields.push('featured_img');
}
if (!fields.length) fields.push('meta_description');
// Run fields sequentially to avoid race conditions
// (multiple optimizers read/write post_content)
var idx = 0;
function runNext() {
if (idx >= fields.length) {
if (seodState.expandedId === postId) {
openSeoModal(postId);
}
$btn.prop('disabled', false).html(origHtml);
return;
}
$.post(ajaxurl, {
action: 'mxchat_seo_suggest',
nonce: mxchatContent.nonce,
post_id: postId,
field: fields[idx],
}).always(function() {
idx++;
runNext();
});
}
runNext();
}).fail(function() {
$btn.prop('disabled', false).html(origHtml);
});
}
function showNotice(message, type) {
$('.mxch-cg-notice').remove();
var typeClass = type === 'error' ? 'mxch-cg-notice-error' : 'mxch-cg-notice-success';
var $notice = $('' + escapeHtml(message) + '
');
$('#mxch-cg-inline-form .mxch-cg-form').prepend($notice);
setTimeout(function() { $notice.fadeOut(300, function() { $(this).remove(); }); }, 4000);
}
// ─── Initialize ────────────────────────────────────────────────────
$(document).ready(function() {
initNavigation();
initInlineForm();
initGeneration();
initPromptModal();
initPreview();
initChat();
initSettingsAutoSave();
initLeftTabs();
initSeo();
initSeoSection();
initHistory();
initStatusDropdown();
// Prevent interaction with locked pro feature toggles
$('.mxch-cg-pro-locked .mxch-toggle-input').on('click', function(e) {
e.preventDefault();
return false;
});
});
})(jQuery);