PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.2.4
MxChat – AI Chatbot & Content Generation for WordPress v3.2.4
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
mxchat-basic / js / mxchat-content.js

mxchat-content.js in MxChat – AI Chatbot & Content Generation for WordPress 3.2.4, at js/mxchat-content.js

2,234 lines 101.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * MxChat Content Generator
3 *
4 * Handles modal generation flow, full-width preview with iframe scaling,
5 * floating chat panel, sidebar navigation, and settings auto-save.
6 *
7 * @package MxChat
8 * @since 3.1.0
9 */
10 (function($) {
11 'use strict';
12
13 var state = {
14 postId: null,
15 previewUrl: null,
16 editUrl: null,
17 permalink: null,
18 progressKey: null,
19 progressPoll: null,
20 isGenerating: false,
21 isEditing: false,
22 chatMessages: [],
23 chatOpen: false,
24 iframeScale: 1,
25 historyLoaded: false,
26 historyPage: 1,
27 historyLoading: false,
28 phraseTimer: null,
29 phraseIndex: 0,
30 pollStartTime: null,
31 customSystemPrompt: '',
32 lastProgressTime: null,
33 postStatus: null
34 };
35
36 var loadingPhrases = [
37 'Consulting our AI overlords...',
38 'Teaching pixels to paint...',
39 'Brewing a fresh pot of creativity...',
40 'Convincing the robots to cooperate...',
41 'Warming up the content engines...',
42 'Negotiating with the algorithm...',
43 'Sprinkling some digital magic...',
44 'Asking ChatGPT to hold our beer...',
45 'Running it through the vibe check...',
46 'Assembling the word wizards...',
47 'Translating brain waves to HTML...',
48 'Polishing every last pixel...',
49 'Taking a quick coffee break...',
50 'Man, this is going to be good...',
51 'Almost there... probably...',
52 'Generating something awesome...',
53 'Feeding the hamsters that power our servers...',
54 'Doing that thing where we look busy...',
55 'Hold tight, genius at work...',
56 'Making the internet a little bit cooler...',
57 'Crafting content so good it should be illegal...',
58 'Our AI designer just said "trust the process"...'
59 ];
60
61 // ─── Sidebar Navigation ────────────────────────────────────────────
62
63 function initNavigation() {
64 $(document).on('click', '.mxch-nav-link[data-target], .mxch-nav-sub-link[data-target]', function(e) {
65 e.preventDefault();
66 var target = $(this).data('target');
67 switchSection(target);
68 $('.mxch-nav-link, .mxch-nav-sub-link').removeClass('active');
69 $(this).addClass('active');
70 });
71
72 $(document).on('click', '.mxch-mobile-nav-link[data-target]', function(e) {
73 e.preventDefault();
74 var target = $(this).data('target');
75 switchSection(target);
76 $('.mxch-mobile-nav-link').removeClass('active');
77 $(this).addClass('active');
78 closeMobileMenu();
79 });
80
81 $(document).on('click', '.mxch-mobile-menu-btn', function() {
82 $('.mxch-mobile-menu, .mxch-mobile-overlay').addClass('open');
83 });
84 $(document).on('click', '.mxch-mobile-menu-close, .mxch-mobile-overlay', function() {
85 closeMobileMenu();
86 });
87 }
88
89 function switchSection(target) {
90 $('.mxch-section').removeClass('active');
91 $('#' + target).addClass('active');
92 }
93
94 function closeMobileMenu() {
95 $('.mxch-mobile-menu, .mxch-mobile-overlay').removeClass('open');
96 }
97
98 // ─── Inline Form ────────────────────────────────────────────────────
99
100 function initInlineForm() {
101 // "Create New" button in toolbar — resets to inline form
102 $('#mxch-cg-new-btn').on('click', function() {
103 resetToForm();
104 });
105
106 // On initial load: form is already visible, hide toolbar and preview-wrap chrome
107 $('#mxch-cg-new-btn').hide();
108 $('.mxch-cg-toolbar').addClass('mxch-cg-toolbar-minimal');
109 $('.mxch-cg-preview-wrap').addClass('mxch-cg-preview-wrap-form');
110 }
111
112 function showInlineForm() {
113 var $form = $('#mxch-cg-inline-form');
114 $form.removeClass('mxch-cg-form-collapsing').show();
115
116 // Hide preview and loading
117 $('#mxch-cg-preview-iframe').hide();
118 $('#mxch-cg-loading-indicator').hide();
119 $('.mxch-cg-preview-wrap').css('height', '');
120
121 // Toolbar: hidden; preview-wrap: transparent
122 $('.mxch-cg-toolbar').addClass('mxch-cg-toolbar-minimal');
123 $('.mxch-cg-preview-wrap').addClass('mxch-cg-preview-wrap-form');
124 $('#mxch-cg-new-btn').hide();
125 $('.mxch-cg-toolbar-right').hide();
126 $('#mxch-cg-status-dropdown').hide();
127 $('#mxch-cg-preview-title').text('Content Generator');
128
129 setTimeout(function() { $('#mxch-cg-prompt').focus(); }, 100);
130 }
131
132 function hideInlineForm() {
133 var $form = $('#mxch-cg-inline-form');
134 $form.addClass('mxch-cg-form-collapsing');
135 setTimeout(function() {
136 $form.hide().removeClass('mxch-cg-form-collapsing');
137 }, 300);
138
139 $('.mxch-cg-toolbar').removeClass('mxch-cg-toolbar-minimal');
140 $('.mxch-cg-preview-wrap').removeClass('mxch-cg-preview-wrap-form');
141 }
142
143 function resetToForm() {
144 // Reset state
145 state.postId = null;
146 state.previewUrl = null;
147 state.editUrl = null;
148 state.permalink = null;
149 state.postStatus = null;
150 state.chatMessages = [];
151
152 closeChatPanel();
153 closeStatusDropdown();
154 resetSeoPanel();
155 $('#mxch-cg-prompt').val('');
156 showInlineForm();
157 }
158
159 // ─── Generation Flow ───────────────────────────────────────────────
160
161 function initGeneration() {
162 // Show/hide schedule date picker
163 $('#mxch-cg-status').on('change', function() {
164 if ($(this).val() === 'future') {
165 $('.mxch-cg-schedule-wrap').show();
166 if (!$('#mxch-cg-schedule').val()) {
167 var tomorrow = new Date();
168 tomorrow.setDate(tomorrow.getDate() + 1);
169 tomorrow.setHours(9, 0, 0, 0);
170 $('#mxch-cg-schedule').val(tomorrow.toISOString().slice(0, 16));
171 }
172 } else {
173 $('.mxch-cg-schedule-wrap').hide();
174 }
175 });
176
177 // Generate button
178 $('#mxch-cg-generate-btn').on('click', function() {
179 if (state.isGenerating) return;
180 startGeneration();
181 });
182 }
183
184 // ── Edit Default Prompt Modal ──────────────────────────────
185 function initPromptModal() {
186 var $modal = $('#mxch-cg-prompt-modal');
187 var $editor = $('#mxch-cg-system-prompt-editor');
188 var $btn = $('#mxch-cg-edit-prompt-btn');
189 var currentDefault = '';
190
191 function fetchPromptData(callback) {
192 var contentType = $('#mxch-cg-type').val() || 'post';
193 $.post(mxchatContent.ajaxUrl, {
194 action: 'mxchat_get_default_prompt',
195 nonce: mxchatContent.nonce,
196 content_type: contentType
197 }, function(response) {
198 if (response.success) {
199 currentDefault = response.data.default_prompt;
200 var saved = response.data.saved_prompt || '';
201 state.customSystemPrompt = saved;
202 updateButtonState();
203 if (callback) callback(currentDefault, saved);
204 }
205 });
206 }
207
208 function updateButtonState() {
209 if (state.customSystemPrompt) {
210 $btn.addClass('mxch-cg-prompt-modified');
211 } else {
212 $btn.removeClass('mxch-cg-prompt-modified');
213 }
214 }
215
216 function openModal() {
217 fetchPromptData(function(def, saved) {
218 $editor.val(saved || def);
219 $modal.fadeIn(200);
220 });
221 }
222
223 function closeModal() {
224 $modal.fadeOut(200);
225 }
226
227 function saveToServer(promptText, callback) {
228 var contentType = $('#mxch-cg-type').val() || 'post';
229 $.post(mxchatContent.ajaxUrl, {
230 action: 'mxchat_save_custom_prompt',
231 nonce: mxchatContent.nonce,
232 content_type: contentType,
233 custom_prompt: promptText
234 }, function(response) {
235 if (callback) callback(response.success);
236 });
237 }
238
239 $btn.on('click', openModal);
240 $('#mxch-cg-prompt-modal-close, #mxch-cg-prompt-cancel, .mxch-cg-prompt-modal-overlay').on('click', closeModal);
241
242 $('#mxch-cg-prompt-save').on('click', function() {
243 var edited = $editor.val().trim();
244 var customValue = (edited && edited !== currentDefault) ? edited : '';
245 state.customSystemPrompt = customValue;
246 saveToServer(customValue);
247 updateButtonState();
248 closeModal();
249 });
250
251 $('#mxch-cg-prompt-reset').on('click', function() {
252 $editor.val(currentDefault);
253 state.customSystemPrompt = '';
254 saveToServer('');
255 updateButtonState();
256 });
257
258 // Load saved state for initial content type on page load
259 fetchPromptData();
260
261 // When content type changes, load the saved prompt for that type
262 $('#mxch-cg-type').on('change', function() {
263 fetchPromptData();
264 });
265 }
266
267 function startGeneration() {
268 var prompt = $('#mxch-cg-prompt').val().trim();
269 if (!prompt) {
270 showNotice('Please enter a prompt describing the content you want to generate.', 'error');
271 return;
272 }
273
274 state.isGenerating = true;
275
276 // Immediately hide form and show loading indicator
277 $('#mxch-cg-inline-form').hide().removeClass('mxch-cg-form-collapsing');
278 $('.mxch-cg-toolbar').removeClass('mxch-cg-toolbar-minimal');
279 $('.mxch-cg-preview-wrap').removeClass('mxch-cg-preview-wrap-form');
280 $('#mxch-cg-preview-title').text('Generating...');
281 $('#mxch-cg-new-btn').hide();
282 $('.mxch-cg-toolbar-right').hide();
283 $('#mxch-cg-status-dropdown').hide();
284 closeStatusDropdown();
285 closeChatPanel();
286 showLoadingIndicator();
287
288 var data = {
289 action: 'mxchat_generate_content',
290 nonce: mxchatContent.nonce,
291 prompt: prompt,
292 content_type: $('#mxch-cg-type').val(),
293 post_status: $('#mxch-cg-status').val(),
294 schedule_date: $('#mxch-cg-schedule').val() || '',
295 layout: $('#mxch-cg-layout').val() || 'fullwidth',
296 title_display: $('#mxch-cg-title-display').val() || 'hide',
297 template_mode: $('#mxch-cg-template-mode').val() || 'off',
298 custom_system_prompt: state.customSystemPrompt || ''
299 };
300
301 $.ajax({
302 url: mxchatContent.ajaxUrl,
303 type: 'POST',
304 data: data,
305 timeout: 60000,
306 success: function(response) {
307 if (response.success && response.data.progress_key) {
308 // Async mode — loading indicator already showing
309 state.progressKey = response.data.progress_key;
310 startProgressPoll();
311 } else if (response.success) {
312 // Sync fallback — full result returned directly
313 onGenerationSuccess(response.data);
314 } else {
315 onGenerationError(response.data && response.data.message ? response.data.message : 'Generation failed.');
316 }
317 },
318 error: function(xhr, status, error) {
319 onGenerationError('Request failed: ' + (error || status));
320 }
321 });
322 }
323
324 function onGenerationSuccess(data) {
325 state.isGenerating = false;
326 state.postId = data.post_id;
327 state.previewUrl = data.preview_url;
328 state.editUrl = data.edit_url;
329 state.permalink = data.permalink;
330 state.postStatus = data.status;
331 state.chatMessages = [];
332 state.historyLoaded = false;
333
334 // Ensure form and its chrome are hidden
335 hideInlineForm();
336
337 // Show success state on loading indicator briefly before showing preview
338 var $loadingIndicator = $('#mxch-cg-loading-indicator');
339 if ($loadingIndicator.is(':visible')) {
340 stopPhraseRotation();
341 $('#mxch-cg-loading-phrase').text('Your content is ready!');
342 updateLoadingProgress(100, 'Complete!');
343 $loadingIndicator.addClass('mxch-cg-loading-success');
344
345 setTimeout(function() {
346 hideLoadingIndicator();
347 finishPreviewLoad(data);
348 }, 1200);
349 } else {
350 // Direct/sync flow — no loading indicator was shown
351 finishPreviewLoad(data);
352 }
353 }
354
355 function finishPreviewLoad(data) {
356 // Update toolbar title
357 $('#mxch-cg-preview-title').text(data.title || 'Preview');
358
359 // Show status dropdown
360 var statusLabels = { draft: 'Draft', publish: 'Published', future: 'Scheduled' };
361 var $dropdown = $('#mxch-cg-status-dropdown');
362 var $badge = $('#mxch-cg-status-badge');
363 $badge.find('.mxch-cg-status-badge-text').text(statusLabels[data.status] || data.status);
364 $badge.removeClass('mxch-cg-badge-draft mxch-cg-badge-publish mxch-cg-badge-future')
365 .addClass('mxch-cg-badge-' + data.status);
366 $dropdown.show();
367 closeStatusDropdown();
368 $('.mxch-cg-status-option').removeClass('mxch-cg-status-active');
369 $('.mxch-cg-status-option[data-status="' + data.status + '"]').addClass('mxch-cg-status-active');
370 state.postStatus = data.status;
371
372 // Show toolbar actions
373 $('.mxch-cg-toolbar-right').show();
374 $('#mxch-cg-view-post').attr('href', data.permalink);
375
376 // Show "Create New" button in toolbar
377 var $newBtn = $('#mxch-cg-new-btn');
378 $newBtn.find('span').text('Create New');
379 $newBtn.show();
380
381 // Load preview
382 loadPreview(data.preview_url);
383
384 // Store post ID on the chat panel for add-on access
385 $('#mxch-cg-chat').attr('data-post-id', data.post_id);
386
387 // Populate image panel with generated images
388 populateImagePanel(data.images || []);
389
390 // Populate meta panel with SEO data
391 populateMetaPanel(data);
392
393 // Auto-run SEO analysis
394 resetSeoPanel();
395 setTimeout(function() { runSeoAnalysis(); }, 500);
396
397 // Pre-populate chat
398 $('#mxch-cg-chat-messages').empty();
399 addChatMessage('assistant', 'Content generated! Request edits like "change the heading to..." or "make the background blue".');
400 }
401
402 function onGenerationError(message) {
403 state.isGenerating = false;
404
405 var $btn = $('#mxch-cg-generate-btn');
406 $btn.prop('disabled', false).removeClass('mxch-cg-loading');
407
408 var $loadingIndicator = $('#mxch-cg-loading-indicator');
409
410 if ($loadingIndicator.is(':visible')) {
411 // Error while loading indicator is showing (async flow)
412 stopPhraseRotation();
413 $loadingIndicator.addClass('mxch-cg-loading-error');
414 $('#mxch-cg-loading-phrase').text('Oops! Something went wrong.');
415 updateLoadingProgress(0, message);
416 $('#mxch-cg-loading-progress-fill').css('background', '#ef4444');
417
418 // Add retry and dismiss buttons
419 if (!$loadingIndicator.find('.mxch-cg-loading-error-actions').length) {
420 var $actions = $(
421 '<div class="mxch-cg-loading-error-actions">' +
422 '<button type="button" class="mxch-cg-generate-btn" id="mxch-cg-loading-retry">' +
423 '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>' +
424 ' Try Again' +
425 '</button>' +
426 '<button type="button" class="mxch-cg-action-btn" id="mxch-cg-loading-dismiss">Dismiss</button>' +
427 '</div>'
428 );
429 $loadingIndicator.append($actions);
430
431 $actions.find('#mxch-cg-loading-retry').on('click', function() {
432 hideLoadingIndicator();
433 showInlineForm();
434 });
435 $actions.find('#mxch-cg-loading-dismiss').on('click', function() {
436 hideLoadingIndicator();
437 showInlineForm();
438 });
439 }
440 } else {
441 // Error while modal is still open (pre-async or sync flow)
442 updateProgress(0, 'Error: ' + message);
443 $('#mxch-cg-progress .mxch-cg-progress-fill').css('background', '#ef4444');
444
445 setTimeout(function() {
446 $('#mxch-cg-progress').fadeOut(300);
447 $('#mxch-cg-progress .mxch-cg-progress-fill').css('background', '');
448 }, 4000);
449 }
450 }
451
452 function updateProgress(percent, message) {
453 $('#mxch-cg-progress .mxch-cg-progress-fill').css('width', percent + '%');
454 $('#mxch-cg-progress .mxch-cg-progress-text').text(message);
455 }
456
457 function startProgressPoll() {
458 // Clear any existing poll interval (but preserve progressKey — it was just set)
459 if (state.progressPoll) {
460 clearInterval(state.progressPoll);
461 state.progressPoll = null;
462 }
463 state.pollStartTime = Date.now();
464 state.lastProgressTime = Date.now();
465 state.progressPoll = setInterval(pollProgress, 2500);
466 }
467
468 function pollProgress() {
469 if (!state.progressKey) return;
470
471 // Activity-based timeout: if no progress update received for 3 minutes, stop.
472 // This allows long generations (many images + long content) to run as long as
473 // the backend is still making progress, while still catching truly stalled jobs.
474 var inactiveMs = Date.now() - (state.lastProgressTime || state.pollStartTime);
475 if (inactiveMs > 180000) {
476 stopProgressPoll();
477 onGenerationError('Generation is taking longer than expected. Check your History tab — the post may have been created.');
478 return;
479 }
480
481 $.ajax({
482 url: mxchatContent.ajaxUrl,
483 type: 'POST',
484 data: {
485 action: 'mxchat_content_progress',
486 nonce: mxchatContent.nonce,
487 progress_key: state.progressKey
488 },
489 timeout: 10000,
490 success: function(response) {
491 if (!response.success) return;
492
493 var d = response.data;
494
495 // Any non-waiting response means the backend is alive — reset inactivity timer
496 if (d.step && d.step !== 'waiting') {
497 state.lastProgressTime = Date.now();
498 }
499
500 updateProgress(d.percent || 0, d.message || 'Processing...');
501 updateLoadingProgress(d.percent || 0, d.message || 'Processing...');
502
503 if (d.step === 'done' && d.result) {
504 stopProgressPoll();
505 onGenerationSuccess(d.result);
506 } else if (d.step === 'error') {
507 stopProgressPoll();
508 onGenerationError(d.message || 'Generation failed.');
509 }
510 },
511 error: function() {
512 // Silently retry on poll failure — don't stop polling
513 }
514 });
515 }
516
517 function stopProgressPoll() {
518 if (state.progressPoll) {
519 clearInterval(state.progressPoll);
520 state.progressPoll = null;
521 }
522 state.progressKey = null;
523 state.pollStartTime = null;
524 state.lastProgressTime = null;
525 }
526
527 // ─── Loading Indicator ────────────────────────────────────────────
528
529 function showLoadingIndicator() {
530 $('#mxch-cg-inline-form').hide().removeClass('mxch-cg-form-collapsing');
531 $('#mxch-cg-preview-iframe').hide();
532 $('.mxch-cg-preview-wrap').css('height', '');
533
534 var $loading = $('#mxch-cg-loading-indicator');
535 $loading
536 .removeClass('mxch-cg-loading-error mxch-cg-loading-success')
537 .show();
538
539 // Reset mini progress
540 $('#mxch-cg-loading-progress-fill').css({ 'width': '0%', 'background': '' });
541 $('#mxch-cg-loading-progress-text').text('Starting...');
542
543 // Remove any leftover error actions
544 $loading.find('.mxch-cg-loading-error-actions').remove();
545
546 startPhraseRotation();
547 }
548
549 function hideLoadingIndicator() {
550 stopPhraseRotation();
551 $('#mxch-cg-loading-indicator').hide();
552 }
553
554 function startPhraseRotation() {
555 stopPhraseRotation();
556
557 // Fisher-Yates shuffle for variety
558 var shuffled = loadingPhrases.slice();
559 for (var i = shuffled.length - 1; i > 0; i--) {
560 var j = Math.floor(Math.random() * (i + 1));
561 var temp = shuffled[i];
562 shuffled[i] = shuffled[j];
563 shuffled[j] = temp;
564 }
565
566 state.phraseIndex = 0;
567 var $phrase = $('#mxch-cg-loading-phrase');
568
569 // Show first phrase immediately
570 $phrase.text(shuffled[0]).removeClass('mxch-cg-phrase-exit mxch-cg-phrase-enter');
571
572 state.phraseTimer = setInterval(function() {
573 state.phraseIndex = (state.phraseIndex + 1) % shuffled.length;
574 var nextText = shuffled[state.phraseIndex];
575
576 // Fade out (slide up)
577 $phrase.addClass('mxch-cg-phrase-exit');
578
579 setTimeout(function() {
580 // Swap text and prepare enter state (below)
581 $phrase
582 .text(nextText)
583 .removeClass('mxch-cg-phrase-exit')
584 .addClass('mxch-cg-phrase-enter');
585
586 // Force reflow then remove enter class to trigger transition
587 $phrase[0].offsetHeight;
588 $phrase.removeClass('mxch-cg-phrase-enter');
589 }, 400); // matches CSS transition duration
590
591 }, 4500);
592 }
593
594 function stopPhraseRotation() {
595 if (state.phraseTimer) {
596 clearInterval(state.phraseTimer);
597 state.phraseTimer = null;
598 }
599 }
600
601 function updateLoadingProgress(percent, message) {
602 $('#mxch-cg-loading-progress-fill').css('width', percent + '%');
603 if (message) {
604 $('#mxch-cg-loading-progress-text').text(message);
605 }
606 }
607
608 // ─── Preview ───────────────────────────────────────────────────────
609
610 function initPreview() {
611 // Viewport toggle
612 $(document).on('click', '.mxch-cg-viewport-btn', function() {
613 var viewport = $(this).data('viewport');
614 $('.mxch-cg-viewport-btn').removeClass('active');
615 $(this).addClass('active');
616
617 var $container = $('#mxch-cg-preview-container');
618 if (viewport === 'mobile') {
619 $container.addClass('mxch-cg-viewport-mobile');
620 // Reset iframe to natural size for mobile
621 $('#mxch-cg-preview-iframe').css({
622 width: '375px',
623 transform: 'none'
624 });
625 } else {
626 $container.removeClass('mxch-cg-viewport-mobile');
627 scaleIframe();
628 }
629 });
630
631 // Recalculate scale on window resize
632 $(window).on('resize', function() {
633 if (!$('#mxch-cg-preview-container').hasClass('mxch-cg-viewport-mobile')) {
634 scaleIframe();
635 }
636 });
637 }
638
639 function scaleIframe() {
640 var $iframe = $('#mxch-cg-preview-iframe');
641 if (!$iframe.is(':visible')) return;
642
643 var $wrap = $('.mxch-cg-preview-wrap');
644 var containerWidth = $wrap.innerWidth();
645 var iframeNativeWidth = 1400;
646
647 if (containerWidth < iframeNativeWidth) {
648 var scale = containerWidth / iframeNativeWidth;
649 state.iframeScale = scale;
650 $iframe.css({
651 width: iframeNativeWidth + 'px',
652 transform: 'scale(' + scale + ')',
653 height: (Math.max(700, $(window).height() - 220) / scale) + 'px'
654 });
655 // Set container height to match scaled iframe
656 $wrap.css('height', ($iframe.outerHeight() * scale) + 'px');
657 } else {
658 state.iframeScale = 1;
659 $iframe.css({
660 width: '100%',
661 transform: 'none',
662 height: Math.max(700, $(window).height() - 220) + 'px'
663 });
664 $wrap.css('height', '');
665 }
666 }
667
668 function loadPreview(url) {
669 var $iframe = $('#mxch-cg-preview-iframe');
670 var $empty = $('.mxch-cg-preview-empty');
671
672 $empty.hide();
673 $iframe.show();
674
675 // Attach load handler BEFORE setting src to avoid race condition
676 $iframe.off('load.scale').on('load.scale', function() {
677 scaleIframe();
678 });
679
680 // Add mxchat_preview param so PHP hides admin bar in <head> before render
681 var separator = url.indexOf('?') !== -1 ? '&' : '?';
682 $iframe.attr('src', url + separator + 'mxchat_preview=1&_t=' + Date.now());
683
684 // Also scale immediately for initial sizing
685 setTimeout(scaleIframe, 100);
686 }
687
688 function refreshPreview() {
689 if (state.previewUrl) {
690 loadPreview(state.previewUrl);
691 }
692 }
693
694 function showPreviewEmpty() {
695 showInlineForm();
696 }
697
698 // ─── Chat Panel ────────────────────────────────────────────────────
699
700 function initChat() {
701 // Toggle chat panel
702 $('#mxch-cg-chat-toggle').on('click', function() {
703 if (state.chatOpen) {
704 closeChatPanel();
705 } else {
706 openChatPanel();
707 }
708 });
709
710 // Close chat panel
711 $('#mxch-cg-chat-close').on('click', function() {
712 closeChatPanel();
713 });
714
715 // Enable/disable send button + auto-resize textarea
716 $('#mxch-cg-chat-input').on('input', function() {
717 var hasText = $(this).val().trim().length > 0;
718 $('#mxch-cg-chat-send').prop('disabled', !hasText || state.isEditing);
719 // Auto-resize
720 this.style.height = 'auto';
721 this.style.height = this.scrollHeight + 'px';
722 });
723
724 // Send on Enter, Shift+Enter for newline
725 $('#mxch-cg-chat-input').on('keydown', function(e) {
726 if (e.key === 'Enter' && !e.shiftKey) {
727 e.preventDefault();
728 if (!$(this).val().trim() || state.isEditing) return;
729 sendEdit();
730 }
731 });
732
733 // Send button click
734 $('#mxch-cg-chat-send').on('click', function() {
735 if (state.isEditing) return;
736 sendEdit();
737 });
738 }
739
740 function openChatPanel() {
741 state.chatOpen = true;
742 // Use flex display for two-column layout
743 $('#mxch-cg-chat').css('display', 'flex');
744 $('#mxch-cg-chat-input').focus();
745 scrollChatToBottom();
746 }
747
748 function closeChatPanel() {
749 state.chatOpen = false;
750 $('#mxch-cg-chat').hide();
751 }
752
753 function sendEdit() {
754 var input = $('#mxch-cg-chat-input').val().trim();
755 if (!input || !state.postId) return;
756
757 state.isEditing = true;
758 $('#mxch-cg-chat-input').val('').css('height', 'auto');
759 $('#mxch-cg-chat-send').prop('disabled', true);
760
761 addChatMessage('user', input);
762
763 var $loading = $('<div class="mxch-cg-chat-msg mxch-cg-chat-assistant"><div class="mxch-cg-chat-bubble mxch-cg-chat-loading"><span></span><span></span><span></span></div></div>');
764 $('#mxch-cg-chat-messages').append($loading);
765 scrollChatToBottom();
766
767 $.ajax({
768 url: mxchatContent.ajaxUrl,
769 type: 'POST',
770 data: {
771 action: 'mxchat_content_edit',
772 nonce: mxchatContent.nonce,
773 post_id: state.postId,
774 edit_instruction: input
775 },
776 timeout: 120000,
777 success: function(response) {
778 $loading.remove();
779 state.isEditing = false;
780
781 if (response.success) {
782 addChatMessage('assistant', response.data.message || 'Content updated.');
783 if (response.data.preview_url) {
784 state.previewUrl = response.data.preview_url;
785 }
786 refreshPreview();
787 if (response.data.title) {
788 $('#mxch-cg-preview-title').text(response.data.title);
789 }
790 if (response.data.meta) {
791 populateMetaPanel(response.data);
792 }
793 if (response.data.images) {
794 populateImagePanel(response.data.images);
795 }
796 } else {
797 addChatMessage('assistant', 'Error: ' + (response.data && response.data.message ? response.data.message : 'Edit failed.'));
798 }
799 },
800 error: function() {
801 $loading.remove();
802 state.isEditing = false;
803 addChatMessage('assistant', 'Error: Request failed. Please try again.');
804 }
805 });
806 }
807
808 function addChatMessage(role, content) {
809 state.chatMessages.push({ role: role, content: content });
810 var roleClass = role === 'user' ? 'mxch-cg-chat-user' : 'mxch-cg-chat-assistant';
811 var $msg = $('<div class="mxch-cg-chat-msg ' + roleClass + '">' +
812 '<div class="mxch-cg-chat-bubble">' + escapeHtml(content) + '</div>' +
813 '</div>');
814 $('#mxch-cg-chat-messages').append($msg);
815 scrollChatToBottom();
816 }
817
818 function scrollChatToBottom() {
819 var el = document.getElementById('mxch-cg-chat-messages');
820 if (el) el.scrollTop = el.scrollHeight;
821 }
822
823 // ─── Settings Auto-Save ────────────────────────────────────────────
824
825 function initSettingsAutoSave() {
826 // Use event delegation so dynamically-enabled fields (e.g. pro toggles
827 // unlocked by add-ons after page load) still trigger saves.
828 $('#content-settings').on('change', '[data-field]', function() {
829 var $field = $(this);
830 var field = $field.data('field');
831 var value;
832
833 if ($field.is(':checkbox')) {
834 value = $field.is(':checked') ? 'on' : 'off';
835 } else {
836 value = $field.val();
837 }
838
839 saveContentSetting(field, value, $field);
840
841 // Keep seoOptimize prefs in sync without page reload
842 var seoMap = {
843 seo_optimize_meta_desc: 'meta_description',
844 seo_optimize_seo_title: 'seo_title',
845 seo_optimize_slug: 'slug',
846 seo_optimize_readability: 'readability',
847 seo_optimize_internal_links: 'internal_links',
848 seo_optimize_img_alt: 'img_alt',
849 seo_optimize_featured_img: 'featured_img'
850 };
851 if (seoMap[field] && mxchatContent.seoOptimize) {
852 mxchatContent.seoOptimize[seoMap[field]] = (value === 'on');
853 }
854 });
855 }
856
857 function saveContentSetting(field, value, $field) {
858 var $label = $field.closest('.mxch-field').find('.mxch-field-label');
859 if (!$label.length) {
860 $label = $field.closest('.mxch-field').find('.mxch-toggle-label');
861 }
862
863 // Show saving spinner
864 if ($label.length) {
865 $label.removeClass('mxch-saved').addClass('mxch-saving');
866 }
867
868 $.ajax({
869 url: mxchatContent.ajaxUrl,
870 type: 'POST',
871 data: {
872 action: 'mxchat_save_content_setting',
873 nonce: mxchatContent.nonce,
874 field: field,
875 value: value
876 },
877 success: function(response) {
878 if ($label.length) {
879 $label.removeClass('mxch-saving');
880 if (response.success) {
881 $label.addClass('mxch-saved');
882 setTimeout(function() {
883 $label.removeClass('mxch-saved');
884 }, 1500);
885 }
886 }
887 },
888 error: function(xhr, status, error) {
889 if ($label.length) {
890 $label.removeClass('mxch-saving');
891 }
892 if (window.console) {
893 console.warn('MxChat content setting save failed:', field, status, error);
894 }
895 }
896 });
897 }
898
899 // ─── Image Panel ────────────────────────────────────────────────────
900
901 function populateImagePanel(images) {
902 var $grid = $('#mxch-cg-images-grid');
903 var $empty = $('#mxch-cg-images-empty');
904 var isLocked = $('.mxch-cg-images-col').hasClass('mxch-cg-pro-locked');
905
906 // Clear any previous images (keep the empty state element)
907 $grid.find('.mxch-cg-image-thumb').remove();
908
909 if (!images || images.length === 0) {
910 $empty.show();
911 return;
912 }
913
914 $empty.hide();
915
916 $.each(images, function(i, img) {
917 var $thumb = $(
918 '<div class="mxch-cg-image-thumb">' +
919 '<img src="' + escapeAttr(img.thumbnail) + '" alt="Image ' + (i + 1) + '">' +
920 '<div class="mxch-cg-image-actions' + (isLocked ? ' mxch-cg-image-actions-locked' : '') + '">' +
921 '<button type="button" class="mxch-cg-image-action-btn mxch-cg-image-upload-btn"' + (isLocked ? ' disabled' : '') + ' data-attachment-id="' + (img.attachment_id || '') + '">' +
922 '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>' +
923 ' Upload' +
924 '</button>' +
925 '<button type="button" class="mxch-cg-image-action-btn mxch-cg-image-regen-btn"' + (isLocked ? ' disabled' : '') + ' data-attachment-id="' + (img.attachment_id || '') + '">' +
926 '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>' +
927 ' Regenerate' +
928 '</button>' +
929 '</div>' +
930 (isLocked ? '<div class="mxch-cg-image-lock-badge"><svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg> PRO</div>' : '') +
931 '</div>'
932 );
933 $grid.append($thumb);
934 });
935 }
936
937 function escapeAttr(str) {
938 return String(str).replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
939 }
940
941 // ─── SEO Tab ────────────────────────────────────────────────────
942
943 var seoState = { analyzed: false, analyzing: false, fixing: false, score: null, checks: null };
944
945 function initSeo() {
946 $('#mxch-seo-analyze').on('click', function() {
947 if (state.postId && !seoState.analyzing) runSeoAnalysis();
948 });
949 $('#mxch-seo-ai-optimize').on('click', function() {
950 if (state.postId && !seoState.fixing) runAiOptimize();
951 });
952 // Per-check AI fix buttons (content editor panel only, not dashboard modal)
953 $(document).on('click', '.mxch-seo-check-fix:not(.mxch-seod-check-fix-btn)', function(e) {
954 e.stopPropagation();
955 var $btn = $(this);
956 if ($btn.hasClass('mxch-seo-check-fixing') || !state.postId) return;
957 runSeoFixSingle($btn.data('field'), $btn);
958 });
959 // Auto-analyze when switching to SEO tab if content exists
960 $(document).on('click', '.mxch-cg-left-tab[data-tab="seo"]', function() {
961 if (state.postId && !seoState.analyzed && !seoState.analyzing) runSeoAnalysis();
962 });
963 }
964
965 function runSeoAnalysis() {
966 if (!state.postId) return;
967 seoState.analyzing = true;
968 $('#mxch-seo-analyze').addClass('mxch-spinning');
969 $.post(ajaxurl, {
970 action: 'mxchat_seo_analyze',
971 nonce: mxchatContent.nonce,
972 post_id: state.postId,
973 }).done(function(res) {
974 if (res.success) {
975 seoState.analyzed = true;
976 seoState.score = res.data.score;
977 seoState.checks = res.data.checks;
978 renderSeoResults(res.data);
979 } else {
980 renderSeoError(res.data || 'Analysis failed');
981 }
982 }).fail(function() {
983 renderSeoError('Connection error');
984 }).always(function() {
985 seoState.analyzing = false;
986 $('#mxch-seo-analyze').removeClass('mxch-spinning');
987 });
988 }
989
990 function renderSeoResults(data) {
991 var score = data.score, checks = data.checks, summary = data.summary;
992 // Score ring
993 var offset = 163.36 - (score / 100) * 163.36;
994 var $ring = $('#mxch-seo-ring');
995 $ring.css('stroke-dashoffset', offset).removeClass('mxch-seo-good mxch-seo-ok mxch-seo-bad');
996 if (score >= 80) $ring.addClass('mxch-seo-good');
997 else if (score >= 50) $ring.addClass('mxch-seo-ok');
998 else $ring.addClass('mxch-seo-bad');
999 $('#mxch-seo-score').text(score);
1000 $('#mxch-seo-score-label').text(score >= 80 ? 'Great' : score >= 60 ? 'Good' : score >= 40 ? 'Needs Work' : 'Poor');
1001 var parts = [];
1002 if (summary.pass) parts.push(summary.pass + ' passed');
1003 if (summary.warn) parts.push(summary.warn + ' warnings');
1004 if (summary.fail) parts.push(summary.fail + ' issues');
1005 $('#mxch-seo-score-summary').text(parts.join(' \u00b7 '));
1006 // Checklist
1007 var $list = $('#mxch-seo-checklist').empty();
1008 var icons = {
1009 pass: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>',
1010 warn: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>',
1011 fail: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
1012 };
1013 // Checks that require the Advanced Content Editor add-on to fix
1014 var addonChecks = { readability: true, internal_links: true, img_alt: true, featured_img: true };
1015 // Map check key → optimize field name for per-check fix buttons
1016 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' };
1017 var sparkleIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 3-1.9 5.8a2 2 0 0 1-1.287 1.288L3 12l5.8 1.9a2 2 0 0 1 1.288 1.287L12 21l1.9-5.8a2 2 0 0 1 1.287-1.288L21 12l-5.8-1.9a2 2 0 0 1-1.288-1.287Z"/></svg>';
1018 var sorted = Object.keys(checks).sort(function(a, b) {
1019 var o = { fail: 0, warn: 1, pass: 2 };
1020 return (o[checks[a].status] || 2) - (o[checks[b].status] || 2);
1021 });
1022 var last = null;
1023 sorted.forEach(function(key) {
1024 var c = checks[key];
1025 if (last && last !== 'pass' && c.status === 'pass') {
1026 $list.append('<div class="mxch-seo-separator"></div>');
1027 }
1028 last = c.status;
1029
1030 // Show addon/pro badge for gated checks that aren't passing
1031 var badge = '';
1032 if (addonChecks[key] && c.status !== 'pass' && !mxchatContent.hasAdvancedContent) {
1033 if (mxchatContent.isActivated) {
1034 badge = ' <a href="https://mxchat.ai/advanced-content-editor/" target="_blank" class="mxch-seod-addon-badge">ADD-ON</a>';
1035 } else {
1036 badge = ' <a href="https://mxchat.ai/" target="_blank" class="mxch-seod-addon-badge mxch-seod-pro-badge">PRO</a>';
1037 }
1038 }
1039
1040 // Per-check AI fix button for non-passing, fixable checks
1041 var fixBtn = '';
1042 if (c.status !== 'pass' && fixableMap[key]) {
1043 var canFix = !addonChecks[key] || mxchatContent.hasAdvancedContent;
1044 if (canFix) {
1045 fixBtn = '<button type="button" class="mxch-seo-check-fix" data-field="' + fixableMap[key] + '" title="AI Fix">' + sparkleIcon + '</button>';
1046 }
1047 }
1048
1049 $list.append(
1050 '<div class="mxch-seo-check" data-check="' + key + '">' +
1051 '<div class="mxch-seo-check-icon mxch-seo-' + c.status + '">' + icons[c.status] + '</div>' +
1052 '<div class="mxch-seo-check-content">' +
1053 '<span class="mxch-seo-check-label">' + escapeHtml(c.label) + badge + '</span>' +
1054 '<span class="mxch-seo-check-detail">' + escapeHtml(c.detail) + '</span>' +
1055 '</div>' +
1056 fixBtn +
1057 '</div>'
1058 );
1059 });
1060 $('#mxch-seo-actions').toggle(summary.fail > 0 || summary.warn > 0);
1061 }
1062
1063 function renderSeoError(msg) {
1064 $('#mxch-seo-checklist').html('<div class="mxch-seo-empty"><span style="color:#ef4444;">' + escapeHtml(msg) + '</span></div>');
1065 }
1066
1067 function runSeoFixSingle(field, $btn) {
1068 $btn.addClass('mxch-seo-check-fixing').prop('disabled', true);
1069 $.post(ajaxurl, {
1070 action: 'mxchat_seo_suggest',
1071 nonce: mxchatContent.nonce,
1072 post_id: state.postId,
1073 field: field,
1074 }).done(function(res) {
1075 if (res.success) {
1076 if (field === 'meta_description') $('#mxch-cg-meta-description').val(res.data.suggestion).trigger('input');
1077 else if (field === 'seo_title') $('#mxch-cg-meta-title').val(res.data.suggestion);
1078 $('.mxch-cg-left-tab[data-tab="meta"]').addClass('mxch-cg-tab-flash');
1079 setTimeout(function() { $('.mxch-cg-left-tab[data-tab="meta"]').removeClass('mxch-cg-tab-flash'); }, 2000);
1080 }
1081 }).always(function() {
1082 $btn.removeClass('mxch-seo-check-fixing').prop('disabled', false);
1083 runSeoAnalysis();
1084 });
1085 }
1086
1087 function runAiOptimize() {
1088 if (!state.postId || seoState.fixing) return;
1089 seoState.fixing = true;
1090 var $btn = $('#mxch-seo-ai-optimize'), origHtml = $btn.html();
1091 $btn.addClass('mxch-seo-fixing').prop('disabled', true)
1092 .html('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 3-1.9 5.8a2 2 0 0 1-1.287 1.288L3 12l5.8 1.9a2 2 0 0 1 1.288 1.287L12 21l1.9-5.8a2 2 0 0 1 1.287-1.288L21 12l-5.8-1.9a2 2 0 0 1-1.288-1.287Z"/></svg> Optimizing\u2026');
1093 var prefs = mxchatContent.seoOptimize || {};
1094 var fields = [];
1095 if (seoState.checks) {
1096 if (prefs.meta_description !== false && seoState.checks.meta_desc && seoState.checks.meta_desc.status !== 'pass') fields.push('meta_description');
1097 if (prefs.seo_title !== false && seoState.checks.title_length && seoState.checks.title_length.status !== 'pass') fields.push('seo_title');
1098 if (prefs.slug !== false && seoState.checks.slug && seoState.checks.slug.status !== 'pass') fields.push('slug');
1099 // Readability, internal links, images require Advanced Content Editor add-on
1100 if (mxchatContent.hasAdvancedContent) {
1101 if (prefs.readability !== false && seoState.checks.readability && seoState.checks.readability.status !== 'pass') fields.push('readability');
1102 if (prefs.internal_links !== false && seoState.checks.internal_links && seoState.checks.internal_links.status !== 'pass') fields.push('internal_links');
1103 if (prefs.img_alt !== false && seoState.checks.img_alt && seoState.checks.img_alt.status !== 'pass') fields.push('img_alt');
1104 if (prefs.featured_img !== false && seoState.checks.featured_img && seoState.checks.featured_img.status !== 'pass') fields.push('featured_img');
1105 }
1106 }
1107 if (!fields.length) fields.push('meta_description');
1108 // Run fields sequentially to avoid race conditions
1109 // (multiple optimizers read/write post_content)
1110 var idx = 0;
1111 function runNext() {
1112 if (idx >= fields.length) {
1113 seoState.fixing = false;
1114 $btn.removeClass('mxch-seo-fixing').prop('disabled', false).html(origHtml);
1115 runSeoAnalysis();
1116 return;
1117 }
1118 var field = fields[idx];
1119 $.post(ajaxurl, {
1120 action: 'mxchat_seo_suggest',
1121 nonce: mxchatContent.nonce,
1122 post_id: state.postId,
1123 field: field,
1124 }).done(function(res) {
1125 if (res.success) {
1126 if (field === 'meta_description') $('#mxch-cg-meta-description').val(res.data.suggestion).trigger('input');
1127 else if (field === 'seo_title') $('#mxch-cg-meta-title').val(res.data.suggestion);
1128 else if (field === 'excerpt') $('#mxch-cg-meta-excerpt').val(res.data.suggestion);
1129 $('.mxch-cg-left-tab[data-tab="meta"]').addClass('mxch-cg-tab-flash');
1130 setTimeout(function() { $('.mxch-cg-left-tab[data-tab="meta"]').removeClass('mxch-cg-tab-flash'); }, 2000);
1131 }
1132 }).always(function() {
1133 idx++;
1134 runNext();
1135 });
1136 }
1137 runNext();
1138 }
1139
1140 function resetSeoPanel() {
1141 seoState = { analyzed: false, analyzing: false, fixing: false, score: null, checks: null };
1142 $('#mxch-seo-score').text('\u2014');
1143 $('#mxch-seo-score-label').text('SEO Score');
1144 $('#mxch-seo-score-summary').text('Generate content to analyze');
1145 $('#mxch-seo-ring').css('stroke-dashoffset', '163.36').removeClass('mxch-seo-good mxch-seo-ok mxch-seo-bad');
1146 $('#mxch-seo-checklist').html(
1147 '<div class="mxch-seo-empty" id="mxch-seo-empty">' +
1148 '<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>' +
1149 '<span>SEO analysis will appear here after content is generated</span></div>'
1150 );
1151 $('#mxch-seo-actions').hide();
1152 }
1153
1154 function escapeHtml(s) {
1155 var d = document.createElement('div');
1156 d.appendChild(document.createTextNode(s));
1157 return d.innerHTML;
1158 }
1159
1160 // ─── Left Column Tabs ──────────────────────────────────────────────
1161
1162 function initLeftTabs() {
1163 $(document).on('click', '.mxch-cg-left-tab', function() {
1164 var tab = $(this).data('tab');
1165 $('.mxch-cg-left-tab').removeClass('active');
1166 $(this).addClass('active');
1167 $('.mxch-cg-left-panel').removeClass('active');
1168 $('#mxch-cg-panel-' + tab).addClass('active');
1169 });
1170
1171 // Character counter for meta description
1172 $(document).on('input', '#mxch-cg-meta-description', updateCharCount);
1173 }
1174
1175 function populateMetaPanel(data) {
1176 $('#mxch-cg-meta-title').val(data.title || '');
1177 if (data.meta) {
1178 $('#mxch-cg-meta-description').val(data.meta.description || '');
1179 $('#mxch-cg-meta-keyword').val(data.meta.keyword || '');
1180 $('#mxch-cg-meta-excerpt').val(data.meta.excerpt || '');
1181 }
1182 updateCharCount();
1183 }
1184
1185 function updateCharCount() {
1186 var len = ($('#mxch-cg-meta-description').val() || '').length;
1187 var $counter = $('.mxch-cg-meta-charcount');
1188 $counter.text(len + ' / 160');
1189 if (len > 160) {
1190 $counter.addClass('mxch-cg-meta-charcount-over');
1191 } else {
1192 $counter.removeClass('mxch-cg-meta-charcount-over');
1193 }
1194 }
1195
1196 // ─── History Tab ──────────────────────────────────────────────────
1197
1198 function initHistory() {
1199 $(document).on('click', '[data-target="content-history"]', function() {
1200 if (!state.historyLoaded) {
1201 loadHistory(1);
1202 }
1203 });
1204
1205 $(document).on('click', '.mxch-cg-history-page-btn[data-page]', function() {
1206 var page = $(this).data('page');
1207 if (page && !state.historyLoading) {
1208 loadHistory(page);
1209 }
1210 });
1211
1212 $(document).on('click', '.mxch-cg-history-edit-btn', function() {
1213 var postId = $(this).data('post-id');
1214 if (postId) {
1215 loadPostForEdit(postId, $(this));
1216 }
1217 });
1218
1219 $(document).on('click', '.mxch-cg-history-delete-btn', function() {
1220 var $btn = $(this);
1221 var postId = $btn.data('post-id');
1222 var $item = $btn.closest('.mxch-cg-history-item');
1223 var title = $item.find('.mxch-cg-history-title').text();
1224
1225 if (!confirm('Move "' + title + '" to trash?')) return;
1226
1227 $btn.prop('disabled', true);
1228 $.ajax({
1229 url: mxchatContent.ajaxUrl,
1230 type: 'POST',
1231 data: {
1232 action: 'mxchat_delete_content',
1233 nonce: mxchatContent.nonce,
1234 post_id: postId
1235 },
1236 success: function(response) {
1237 if (response.success) {
1238 $item.slideUp(200, function() { $(this).remove(); });
1239 } else {
1240 alert(response.data.message || 'Failed to delete.');
1241 $btn.prop('disabled', false);
1242 }
1243 },
1244 error: function() {
1245 alert('Request failed. Please try again.');
1246 $btn.prop('disabled', false);
1247 }
1248 });
1249 });
1250 }
1251
1252 function loadHistory(page) {
1253 state.historyLoading = true;
1254 state.historyPage = page;
1255
1256 var $loading = $('#mxch-cg-history-loading');
1257 var $empty = $('#mxch-cg-history-empty');
1258 var $list = $('#mxch-cg-history-list');
1259 var $pag = $('#mxch-cg-history-pagination');
1260
1261 $loading.show();
1262 $empty.hide();
1263 $list.hide();
1264 $pag.hide();
1265
1266 $.ajax({
1267 url: mxchatContent.ajaxUrl,
1268 type: 'POST',
1269 data: {
1270 action: 'mxchat_content_history',
1271 nonce: mxchatContent.nonce,
1272 page: page
1273 },
1274 success: function(response) {
1275 state.historyLoading = false;
1276 state.historyLoaded = true;
1277 $loading.hide();
1278
1279 if (!response.success || !response.data.items.length) {
1280 $empty.show();
1281 return;
1282 }
1283
1284 renderHistoryList(response.data.items);
1285 renderHistoryPagination(response.data.current_page, response.data.total_pages);
1286 $list.show();
1287
1288 if (response.data.total_pages > 1) {
1289 $pag.show();
1290 }
1291 },
1292 error: function() {
1293 state.historyLoading = false;
1294 $loading.hide();
1295 $empty.show();
1296 }
1297 });
1298 }
1299
1300 function renderHistoryList(items) {
1301 var $list = $('#mxch-cg-history-list');
1302 $list.empty();
1303
1304 var statusLabels = {
1305 draft: 'Draft',
1306 publish: 'Published',
1307 future: 'Scheduled',
1308 pending: 'Pending',
1309 'private': 'Private'
1310 };
1311
1312 $.each(items, function(i, item) {
1313 var thumbHtml = item.thumbnail
1314 ? '<img src="' + escapeAttr(item.thumbnail) + '" alt="" class="mxch-cg-history-thumb-img">'
1315 : '<div class="mxch-cg-history-thumb-placeholder">' +
1316 '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>' +
1317 '</div>';
1318
1319 var statusClass = 'mxch-cg-badge-' + item.status;
1320 var statusText = statusLabels[item.status] || item.status;
1321 var typeLabel = item.post_type === 'page' ? 'Page' : 'Post';
1322
1323 var $row = $(
1324 '<div class="mxch-cg-history-item">' +
1325 '<div class="mxch-cg-history-thumb">' + thumbHtml + '</div>' +
1326 '<div class="mxch-cg-history-info">' +
1327 '<div class="mxch-cg-history-title">' + escapeHtml(item.title) + '</div>' +
1328 '<div class="mxch-cg-history-meta">' +
1329 '<span class="mxch-cg-status-badge ' + statusClass + '">' + escapeHtml(statusText) + '</span>' +
1330 '<span class="mxch-cg-history-type">' + escapeHtml(typeLabel) + '</span>' +
1331 '<span class="mxch-cg-history-date">' + escapeHtml(item.date) + '</span>' +
1332 '</div>' +
1333 '</div>' +
1334 '<div class="mxch-cg-history-actions">' +
1335 '<a href="' + escapeAttr(item.permalink) + '" target="_blank" class="mxch-cg-history-action-btn mxch-cg-history-view-btn" title="View">' +
1336 '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>' +
1337 '</a>' +
1338 '<button type="button" class="mxch-cg-history-action-btn mxch-cg-history-edit-btn" data-post-id="' + item.post_id + '" title="Edit">' +
1339 '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg>' +
1340 '</button>' +
1341 '<button type="button" class="mxch-cg-history-action-btn mxch-cg-history-delete-btn" data-post-id="' + item.post_id + '" title="Delete">' +
1342 '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>' +
1343 '</button>' +
1344 '</div>' +
1345 '</div>'
1346 );
1347
1348 $list.append($row);
1349 });
1350 }
1351
1352 function renderHistoryPagination(current, total) {
1353 var $pag = $('#mxch-cg-history-pagination');
1354 $pag.empty();
1355
1356 if (total <= 1) return;
1357
1358 var html = '';
1359
1360 if (current > 1) {
1361 html += '<button type="button" class="mxch-cg-history-page-btn mxch-cg-history-page-prev" data-page="' + (current - 1) + '">&laquo; Prev</button>';
1362 }
1363
1364 for (var p = 1; p <= total; p++) {
1365 if (p === current) {
1366 html += '<span class="mxch-cg-history-page-btn mxch-cg-history-page-current">' + p + '</span>';
1367 } else if (p === 1 || p === total || (p >= current - 1 && p <= current + 1)) {
1368 html += '<button type="button" class="mxch-cg-history-page-btn" data-page="' + p + '">' + p + '</button>';
1369 } else if (p === current - 2 || p === current + 2) {
1370 html += '<span class="mxch-cg-history-page-ellipsis">&hellip;</span>';
1371 }
1372 }
1373
1374 if (current < total) {
1375 html += '<button type="button" class="mxch-cg-history-page-btn mxch-cg-history-page-next" data-page="' + (current + 1) + '">Next &raquo;</button>';
1376 }
1377
1378 $pag.html(html);
1379 }
1380
1381 function loadPostForEdit(postId, $btn) {
1382 var editBtnHtml = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z"/></svg> Edit';
1383
1384 $btn.prop('disabled', true).text('Loading...');
1385
1386 $.ajax({
1387 url: mxchatContent.ajaxUrl,
1388 type: 'POST',
1389 data: {
1390 action: 'mxchat_load_post_for_edit',
1391 nonce: mxchatContent.nonce,
1392 post_id: postId
1393 },
1394 success: function(response) {
1395 $btn.prop('disabled', false).html(editBtnHtml);
1396
1397 if (response.success) {
1398 // Switch to Generate tab
1399 switchSection('content-generate');
1400 $('.mxch-nav-link, .mxch-nav-sub-link').removeClass('active');
1401 $('[data-target="content-generate"]').addClass('active');
1402 $('.mxch-mobile-nav-link').removeClass('active');
1403 $('.mxch-mobile-nav-link[data-target="content-generate"]').addClass('active');
1404
1405 // Load post into the same editor state as fresh generation
1406 onGenerationSuccess(response.data);
1407 } else {
1408 alert(response.data && response.data.message ? response.data.message : 'Failed to load post.');
1409 }
1410 },
1411 error: function() {
1412 $btn.prop('disabled', false).html(editBtnHtml);
1413 alert('Request failed. Please try again.');
1414 }
1415 });
1416 }
1417
1418 // ─── Status Dropdown ──────────────────────────────────────────────
1419
1420 function initStatusDropdown() {
1421 // Toggle dropdown on badge click
1422 $(document).on('click', '#mxch-cg-status-badge', function(e) {
1423 e.stopPropagation();
1424 var $dropdown = $('#mxch-cg-status-dropdown');
1425 if ($dropdown.hasClass('mxch-cg-dropdown-open')) {
1426 closeStatusDropdown();
1427 } else {
1428 openStatusDropdown();
1429 }
1430 });
1431
1432 // Close on outside click
1433 $(document).on('click', function(e) {
1434 if (!$(e.target).closest('#mxch-cg-status-dropdown').length) {
1435 closeStatusDropdown();
1436 }
1437 });
1438
1439 // Close on Escape
1440 $(document).on('keydown', function(e) {
1441 if (e.key === 'Escape') {
1442 closeStatusDropdown();
1443 }
1444 });
1445
1446 // Draft / Publish — immediate status change
1447 $(document).on('click', '.mxch-cg-status-option[data-status="draft"], .mxch-cg-status-option[data-status="publish"]', function() {
1448 var newStatus = $(this).data('status');
1449 if (newStatus === state.postStatus) {
1450 closeStatusDropdown();
1451 return;
1452 }
1453 updatePostStatus(newStatus, '');
1454 });
1455
1456 // Scheduled — show datetime picker
1457 $(document).on('click', '.mxch-cg-status-option[data-status="future"]', function() {
1458 var $scheduleRow = $('.mxch-cg-status-schedule-row');
1459 if ($scheduleRow.is(':visible')) {
1460 $scheduleRow.hide();
1461 return;
1462 }
1463 // Pre-fill with tomorrow at 9am if empty
1464 var $input = $('#mxch-cg-status-schedule-input');
1465 if (!$input.val()) {
1466 var tomorrow = new Date();
1467 tomorrow.setDate(tomorrow.getDate() + 1);
1468 tomorrow.setHours(9, 0, 0, 0);
1469 $input.val(tomorrow.toISOString().slice(0, 16));
1470 }
1471 $scheduleRow.show();
1472 $input.focus();
1473 // Highlight scheduled option
1474 $('.mxch-cg-status-option').removeClass('mxch-cg-status-active');
1475 $(this).addClass('mxch-cg-status-active');
1476 });
1477
1478 // Confirm schedule
1479 $(document).on('click', '#mxch-cg-status-schedule-confirm', function() {
1480 var scheduleDate = $('#mxch-cg-status-schedule-input').val();
1481 if (!scheduleDate) {
1482 $('#mxch-cg-status-schedule-input').focus();
1483 return;
1484 }
1485 // Convert datetime-local value to WordPress format (Y-m-d H:i:s)
1486 var wpDate = scheduleDate.replace('T', ' ') + ':00';
1487 updatePostStatus('future', wpDate);
1488 });
1489
1490 // Enter key on datetime input confirms
1491 $(document).on('keydown', '#mxch-cg-status-schedule-input', function(e) {
1492 if (e.key === 'Enter') {
1493 e.preventDefault();
1494 $('#mxch-cg-status-schedule-confirm').trigger('click');
1495 }
1496 });
1497 }
1498
1499 function openStatusDropdown() {
1500 var $dropdown = $('#mxch-cg-status-dropdown');
1501 $dropdown.addClass('mxch-cg-dropdown-open');
1502 $('.mxch-cg-status-menu').show();
1503 // Highlight current status
1504 $('.mxch-cg-status-option').removeClass('mxch-cg-status-active');
1505 $('.mxch-cg-status-option[data-status="' + state.postStatus + '"]').addClass('mxch-cg-status-active');
1506 // Hide schedule row unless current status is future
1507 if (state.postStatus !== 'future') {
1508 $('.mxch-cg-status-schedule-row').hide();
1509 }
1510 }
1511
1512 function closeStatusDropdown() {
1513 $('#mxch-cg-status-dropdown').removeClass('mxch-cg-dropdown-open');
1514 $('.mxch-cg-status-menu').hide();
1515 $('.mxch-cg-status-schedule-row').hide();
1516 }
1517
1518 function updatePostStatus(newStatus, scheduleDate) {
1519 var $badge = $('#mxch-cg-status-badge');
1520 $badge.addClass('mxch-cg-status-updating');
1521 closeStatusDropdown();
1522
1523 $.ajax({
1524 url: mxchatContent.ajaxUrl,
1525 type: 'POST',
1526 data: {
1527 action: 'mxchat_update_post_status',
1528 nonce: mxchatContent.nonce,
1529 post_id: state.postId,
1530 new_status: newStatus,
1531 schedule_date: scheduleDate || ''
1532 },
1533 success: function(response) {
1534 $badge.removeClass('mxch-cg-status-updating');
1535
1536 if (response.success) {
1537 var confirmedStatus = response.data.status;
1538 state.postStatus = confirmedStatus;
1539
1540 // Update badge appearance
1541 var statusLabels = { draft: 'Draft', publish: 'Published', future: 'Scheduled' };
1542 $badge.find('.mxch-cg-status-badge-text').text(statusLabels[confirmedStatus] || confirmedStatus);
1543 $badge.removeClass('mxch-cg-badge-draft mxch-cg-badge-publish mxch-cg-badge-future')
1544 .addClass('mxch-cg-badge-' + confirmedStatus);
1545
1546 // Mark history as stale so it reloads on next visit
1547 state.historyLoaded = false;
1548
1549 // Refresh preview (URL may differ between draft/published)
1550 refreshPreview();
1551 } else {
1552 alert(response.data && response.data.message ? response.data.message : 'Failed to update status.');
1553 }
1554 },
1555 error: function() {
1556 $badge.removeClass('mxch-cg-status-updating');
1557 alert('Request failed. Please try again.');
1558 }
1559 });
1560 }
1561
1562 // ─── Utilities ─────────────────────────────────────────────────────
1563
1564 function escapeHtml(str) {
1565 var div = document.createElement('div');
1566 div.appendChild(document.createTextNode(str));
1567 return div.innerHTML;
1568 }
1569
1570 // ─── SEO Dashboard (Site-wide) ─────────────────────────────────────
1571
1572 var seodState = {
1573 loaded: false,
1574 loading: false,
1575 page: 1,
1576 pages: 1,
1577 total: 0,
1578 filter: 'all',
1579 postType: 'any',
1580 search: '',
1581 searchTimer: null,
1582 scanning: false,
1583 expandedId: null,
1584 expandAnalyzing: false,
1585 sortBy: 'date',
1586 sortOrder: 'DESC',
1587 };
1588
1589 function initSeoSection() {
1590 // Lazy-load: fetch posts when user first visits SEO section
1591 $(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() {
1592 if (!seodState.loaded && !seodState.loading) {
1593 loadSeoPosts();
1594 }
1595 });
1596
1597 // Filter pills
1598 $(document).on('click', '.mxch-seod-pill', function() {
1599 $('.mxch-seod-pill').removeClass('active');
1600 $(this).addClass('active');
1601 seodState.filter = $(this).data('filter');
1602 seodState.page = 1;
1603 loadSeoPosts();
1604 });
1605
1606 // Post type dropdown
1607 $(document).on('change', '#mxch-seod-post-type', function() {
1608 seodState.postType = $(this).val();
1609 seodState.page = 1;
1610 loadSeoPosts();
1611 });
1612
1613 // Search with debounce
1614 $(document).on('input', '#mxch-seod-search', function() {
1615 var val = $(this).val();
1616 clearTimeout(seodState.searchTimer);
1617 seodState.searchTimer = setTimeout(function() {
1618 seodState.search = val;
1619 seodState.page = 1;
1620 loadSeoPosts();
1621 }, 400);
1622 });
1623
1624 // Pagination
1625 $(document).on('click', '.mxch-seod-page-btn', function() {
1626 var p = $(this).data('page');
1627 if (p && p !== seodState.page) {
1628 seodState.page = p;
1629 loadSeoPosts();
1630 }
1631 });
1632
1633 // Open detail modal on row click
1634 $(document).on('click', '.mxch-seod-row', function() {
1635 var postId = $(this).data('post-id');
1636 openSeoModal(postId);
1637 });
1638
1639 // Close modal
1640 $(document).on('click', '.mxch-seod-modal-overlay', function(e) {
1641 if ($(e.target).hasClass('mxch-seod-modal-overlay')) closeSeoModal();
1642 });
1643 $(document).on('click', '.mxch-seod-modal-close', function() {
1644 closeSeoModal();
1645 });
1646 $(document).on('keydown', function(e) {
1647 if (e.key === 'Escape' && seodState.expandedId) closeSeoModal();
1648 });
1649
1650 // Scan Unscored button
1651 $(document).on('click', '#mxch-seod-scan-all', function() {
1652 if (!seodState.scanning) bulkSeoScan();
1653 });
1654 // Stop scan button
1655 $(document).on('click', '#mxch-seod-scan-stop', function() {
1656 seodState.scanAborted = true;
1657 $(this).prop('disabled', true).find('span').text('Stopping...');
1658 });
1659
1660 // Sortable column headers (all columns sort server-side)
1661 $(document).on('click', '.mxch-seod-header-cell[data-sort]', function() {
1662 var col = $(this).data('sort');
1663 if (seodState.sortBy === col) {
1664 seodState.sortOrder = seodState.sortOrder === 'DESC' ? 'ASC' : 'DESC';
1665 } else {
1666 seodState.sortBy = col;
1667 seodState.sortOrder = col === 'title' ? 'ASC' : 'DESC';
1668 }
1669 seodState.page = 1;
1670 loadSeoPosts();
1671 });
1672
1673 // AI Optimize within detail modal
1674 $(document).on('click', '.mxch-seod-optimize-btn', function(e) {
1675 e.stopPropagation();
1676 var postId = $(this).closest('.mxch-seod-detail').data('post-id');
1677 runSeodOptimize(postId, $(this));
1678 });
1679
1680 // Per-check AI fix buttons in detail modal
1681 $(document).on('click', '.mxch-seod-check-fix-btn', function(e) {
1682 e.stopPropagation();
1683 var $btn = $(this);
1684 if ($btn.hasClass('mxch-seo-check-fixing')) return;
1685 var field = $btn.data('field');
1686 var postId = $btn.data('post-id');
1687 $btn.addClass('mxch-seo-check-fixing').prop('disabled', true);
1688 $.post(ajaxurl, {
1689 action: 'mxchat_seo_suggest',
1690 nonce: mxchatContent.nonce,
1691 post_id: postId,
1692 field: field,
1693 }).always(function() {
1694 $btn.removeClass('mxch-seo-check-fixing').prop('disabled', false);
1695 if (seodState.expandedId === postId) {
1696 openSeoModal(postId);
1697 }
1698 });
1699 });
1700
1701 // Checkbox: prevent row click when clicking checkbox
1702 $(document).on('click', '.mxch-seod-cell-check, .mxch-seod-header-check', function(e) {
1703 e.stopPropagation();
1704 });
1705
1706 // Select all checkbox
1707 $(document).on('change', '.mxch-seod-check-all', function() {
1708 var checked = $(this).prop('checked');
1709 $('.mxch-seod-row-check').prop('checked', checked);
1710 updateOptimizeSelectedBtn();
1711 });
1712
1713 // Individual row checkbox
1714 $(document).on('change', '.mxch-seod-row-check', function() {
1715 var allChecked = $('.mxch-seod-row-check').length === $('.mxch-seod-row-check:checked').length;
1716 $('.mxch-seod-check-all').prop('checked', allChecked);
1717 updateOptimizeSelectedBtn();
1718 });
1719 }
1720
1721 function updateOptimizeSelectedBtn() {
1722 var count = $('.mxch-seod-row-check:checked').length;
1723 var $btn = $('#mxch-seod-optimize-selected');
1724 var $note = $('#mxch-seod-bulk-note');
1725 var isLocked = $btn.hasClass('mxch-seod-bulk-locked');
1726 if (count > 0) {
1727 $btn.find('span').first().text(isLocked ? 'Bulk Optimize' : 'Optimize Selected (' + count + ')');
1728 $btn.show();
1729 if (isLocked) $note.show();
1730 } else {
1731 $btn.hide();
1732 $note.hide();
1733 }
1734 }
1735
1736 function loadSeoPosts() {
1737 seodState.loading = true;
1738 $('#mxch-seod-loading').show();
1739 $('#mxch-seod-empty').hide();
1740 $('#mxch-seod-table').empty();
1741
1742 $.post(ajaxurl, {
1743 action: 'mxchat_seo_list_posts',
1744 nonce: mxchatContent.nonce,
1745 page: seodState.page,
1746 post_type: seodState.postType,
1747 filter: seodState.filter,
1748 search: seodState.search,
1749 sort_by: seodState.sortBy,
1750 sort_order: seodState.sortOrder,
1751 }).done(function(res) {
1752 if (res.success) {
1753 seodState.loaded = true;
1754 seodState.page = res.data.page;
1755 seodState.pages = res.data.pages;
1756 seodState.total = res.data.total;
1757 renderSeodTable(res.data.posts);
1758 renderSeodPagination();
1759 updateSeodScanBtn(res.data.unscored_count);
1760 $('#mxch-seod-footer').show();
1761 if (!res.data.posts.length) {
1762 $('#mxch-seod-empty').show();
1763 }
1764 }
1765 }).fail(function() {
1766 $('#mxch-seod-table').html('<div class="mxch-seod-error">Failed to load posts. Please try again.</div>');
1767 }).always(function() {
1768 seodState.loading = false;
1769 $('#mxch-seod-loading').hide();
1770 });
1771 }
1772
1773 function seodFormatNum(n) {
1774 if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
1775 if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
1776 return n;
1777 }
1778
1779 function seodSortArrow(col) {
1780 if (seodState.sortBy !== col) return '';
1781 return ' <span class="mxch-seod-sort-arrow">' + (seodState.sortOrder === 'ASC' ? '&#9650;' : '&#9660;') + '</span>';
1782 }
1783
1784 function renderSeodTable(posts) {
1785 var $table = $('#mxch-seod-table');
1786 $table.empty();
1787 seodState.expandedId = null;
1788
1789 // Column headers
1790 var activeClass = function(col) { return seodState.sortBy === col ? ' mxch-seod-header-active' : ''; };
1791 $table.append(
1792 '<div class="mxch-seod-header">' +
1793 '<div class="mxch-seod-header-cell mxch-seod-header-check"><input type="checkbox" class="mxch-seod-check-all" title="Select all"></div>' +
1794 '<div class="mxch-seod-header-cell mxch-seod-header-title' + activeClass('title') + '" data-sort="title">Title' + seodSortArrow('title') + '</div>' +
1795 '<div class="mxch-seod-header-cell mxch-seod-header-date' + activeClass('date') + '" data-sort="date">Date' + seodSortArrow('date') + '</div>' +
1796 '<div class="mxch-seod-header-cell mxch-seod-header-score' + activeClass('score') + '" data-sort="score">Score' + seodSortArrow('score') + '</div>' +
1797 '<div class="mxch-seod-header-cell mxch-seod-header-clicks' + (!mxchatContent.hasGSC ? ' mxch-seod-header-locked' : '') + '"' + (mxchatContent.hasGSC ? ' data-sort="clicks"' : '') + '>Clicks' + (mxchatContent.hasGSC ? seodSortArrow('clicks') : ' <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>') + '</div>' +
1798 '<div class="mxch-seod-header-cell mxch-seod-header-impressions' + (!mxchatContent.hasGSC ? ' mxch-seod-header-locked' : '') + '"' + (mxchatContent.hasGSC ? ' data-sort="impressions"' : '') + '>Impr.' + (mxchatContent.hasGSC ? seodSortArrow('impressions') : ' <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>') + '</div>' +
1799 '</div>'
1800 );
1801
1802 posts.forEach(function(p) {
1803 var scoreHtml;
1804 if (p.score !== null) {
1805 var cls = p.score >= 80 ? 'mxch-seod-good' : p.score >= 50 ? 'mxch-seod-ok' : 'mxch-seod-bad';
1806 scoreHtml = '<div class="mxch-seod-score-badge ' + cls + '">' + p.score + '</div>';
1807 } else {
1808 scoreHtml = '<div class="mxch-seod-score-badge mxch-seod-unscored">&mdash;</div>';
1809 }
1810
1811 var typeLabel = p.type.charAt(0).toUpperCase() + p.type.slice(1);
1812
1813 $table.append(
1814 '<div class="mxch-seod-row" data-post-id="' + p.id + '" data-score="' + (p.score !== null ? p.score : -1) + '" data-permalink="' + escapeAttr(p.permalink) + '">' +
1815 '<div class="mxch-seod-row-main">' +
1816 '<div class="mxch-seod-cell mxch-seod-cell-check"><input type="checkbox" class="mxch-seod-row-check" data-post-id="' + p.id + '"></div>' +
1817 '<div class="mxch-seod-cell mxch-seod-cell-title">' +
1818 '<span class="mxch-seod-title">' + escapeHtml(p.title) + '</span>' +
1819 '<span class="mxch-seod-meta">' + typeLabel + '</span>' +
1820 '</div>' +
1821 '<div class="mxch-seod-cell mxch-seod-cell-date">' +
1822 '<span class="mxch-seod-date">' + escapeHtml(p.date) + '</span>' +
1823 '</div>' +
1824 '<div class="mxch-seod-cell mxch-seod-cell-score">' + scoreHtml + '</div>' +
1825 '<div class="mxch-seod-cell mxch-seod-cell-clicks' + (!mxchatContent.hasGSC ? ' mxch-seod-cell-locked' : '') + '" data-clicks="' + (p.clicks !== null ? p.clicks : 0) + '">' + (!mxchatContent.hasGSC ? '' : (p.clicks !== null ? p.clicks : '')) + '</div>' +
1826 '<div class="mxch-seod-cell mxch-seod-cell-impressions' + (!mxchatContent.hasGSC ? ' mxch-seod-cell-locked' : '') + '" data-impressions="' + (p.impressions !== null ? p.impressions : 0) + '">' + (!mxchatContent.hasGSC ? '' : (p.impressions !== null ? seodFormatNum(p.impressions) : '')) + '</div>' +
1827 '</div>' +
1828 '</div>'
1829 );
1830 });
1831 }
1832
1833 function renderSeodPagination() {
1834 var $pag = $('#mxch-seod-pagination');
1835 $pag.empty();
1836
1837 if (seodState.pages <= 1) return;
1838
1839 var p = seodState.page, total = seodState.pages;
1840
1841 if (p > 1) {
1842 $pag.append('<button type="button" class="mxch-seod-page-btn" data-page="' + (p - 1) + '">&larr; Prev</button>');
1843 }
1844 $pag.append('<span class="mxch-seod-page-info">Page ' + p + ' of ' + total + '</span>');
1845 if (p < total) {
1846 $pag.append('<button type="button" class="mxch-seod-page-btn" data-page="' + (p + 1) + '">Next &rarr;</button>');
1847 }
1848 }
1849
1850 function updateSeodScanBtn(unscoredCount) {
1851 var $btn = $('#mxch-seod-scan-all');
1852 if (unscoredCount > 0) {
1853 $btn.show().text('Scan Unscored (' + unscoredCount + ')');
1854 } else {
1855 $btn.hide();
1856 }
1857 $('#mxch-seod-scan-status').text('');
1858 }
1859
1860 function openSeoModal(postId) {
1861 closeSeoModal(); // close any existing modal
1862 seodState.expandedId = postId;
1863
1864 var $row = $('.mxch-seod-row[data-post-id="' + postId + '"]');
1865 var title = $row.find('.mxch-seod-title').text() || 'Post #' + postId;
1866 var permalink = $row.attr('data-permalink') || '';
1867
1868 var $overlay = $(
1869 '<div class="mxch-seod-modal-overlay">' +
1870 '<div class="mxch-seod-modal">' +
1871 '<div class="mxch-seod-modal-header">' +
1872 '<h3 class="mxch-seod-modal-title">' + escapeHtml(title) + '</h3>' +
1873 (permalink ? '<a href="' + escapeAttr(permalink) + '" target="_blank" class="mxch-seod-modal-view-page" title="View Page">' +
1874 '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>' +
1875 ' View Page</a>' : '') +
1876 '<button type="button" class="mxch-seod-modal-close" title="Close">' +
1877 '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>' +
1878 '</button>' +
1879 '</div>' +
1880 '<div class="mxch-seod-modal-body">' +
1881 '<div class="mxch-seod-detail" data-post-id="' + postId + '">' +
1882 '<div class="mxch-seod-detail-loading">' +
1883 '<div class="mxch-seod-spinner"></div>' +
1884 '<span>Analyzing&hellip;</span>' +
1885 '</div>' +
1886 '</div>' +
1887 '</div>' +
1888 '</div>' +
1889 '</div>'
1890 );
1891
1892 $('body').append($overlay);
1893 // Trigger reflow then add visible class for animation
1894 $overlay[0].offsetHeight;
1895 $overlay.addClass('mxch-seod-modal-visible');
1896
1897 var $detail = $overlay.find('.mxch-seod-detail');
1898
1899 // Run analysis
1900 seodState.expandAnalyzing = true;
1901 $.post(ajaxurl, {
1902 action: 'mxchat_seo_analyze',
1903 nonce: mxchatContent.nonce,
1904 post_id: postId,
1905 }).done(function(res) {
1906 if (res.success) {
1907 renderSeodDetail($detail, res.data, postId);
1908 // Update the row's score badge in the table too
1909 var score = res.data.score;
1910 var cls = score >= 80 ? 'mxch-seod-good' : score >= 50 ? 'mxch-seod-ok' : 'mxch-seod-bad';
1911 $row.find('.mxch-seod-score-badge')
1912 .removeClass('mxch-seod-good mxch-seod-ok mxch-seod-bad mxch-seod-unscored')
1913 .addClass(cls).text(score);
1914 } else {
1915 $detail.html('<div class="mxch-seod-detail-error">Analysis failed. Please try again.</div>');
1916 }
1917 }).fail(function() {
1918 $detail.html('<div class="mxch-seod-detail-error">Connection error. Please try again.</div>');
1919 }).always(function() {
1920 seodState.expandAnalyzing = false;
1921 });
1922 }
1923
1924 function renderSeodDetail($detail, data, postId) {
1925 var checks = data.checks, score = data.score, summary = data.summary;
1926 var icons = {
1927 pass: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>',
1928 warn: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>',
1929 fail: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',
1930 };
1931
1932 // Checks that require the Advanced Content Editor add-on to fix
1933 var addonChecks = { readability: true, internal_links: true, img_alt: true, featured_img: true };
1934 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' };
1935 var sparkleIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 3-1.9 5.8a2 2 0 0 1-1.287 1.288L3 12l5.8 1.9a2 2 0 0 1 1.288 1.287L12 21l1.9-5.8a2 2 0 0 1 1.287-1.288L21 12l-5.8-1.9a2 2 0 0 1-1.288-1.287Z"/></svg>';
1936
1937 var sorted = Object.keys(checks).sort(function(a, b) {
1938 var o = { fail: 0, warn: 1, pass: 2 };
1939 return (o[checks[a].status] || 2) - (o[checks[b].status] || 2);
1940 });
1941
1942 var html = '<div class="mxch-seod-checks">';
1943 var last = null;
1944 sorted.forEach(function(key) {
1945 var c = checks[key];
1946 if (last && last !== 'pass' && c.status === 'pass') {
1947 html += '<div class="mxch-seod-check-sep"></div>';
1948 }
1949 last = c.status;
1950
1951 // Show addon/pro badge for gated checks that aren't passing
1952 var badge = '';
1953 if (addonChecks[key] && c.status !== 'pass' && !mxchatContent.hasAdvancedContent) {
1954 if (mxchatContent.isActivated) {
1955 badge = ' <a href="https://mxchat.ai/advanced-content-editor/" target="_blank" class="mxch-seod-addon-badge">ADD-ON</a>';
1956 } else {
1957 badge = ' <a href="https://mxchat.ai/" target="_blank" class="mxch-seod-addon-badge mxch-seod-pro-badge">PRO</a>';
1958 }
1959 }
1960
1961 // Per-check AI fix button
1962 var fixBtn = '';
1963 if (c.status !== 'pass' && fixableMap[key]) {
1964 var canFix = !addonChecks[key] || mxchatContent.hasAdvancedContent;
1965 if (canFix) {
1966 fixBtn = '<button type="button" class="mxch-seo-check-fix mxch-seod-check-fix-btn" data-field="' + fixableMap[key] + '" data-post-id="' + postId + '" title="AI Fix">' + sparkleIcon + '</button>';
1967 }
1968 }
1969
1970 html += '<div class="mxch-seod-check mxch-seod-check-' + c.status + '">' +
1971 '<div class="mxch-seod-check-icon">' + icons[c.status] + '</div>' +
1972 '<div class="mxch-seod-check-text">' +
1973 '<span class="mxch-seod-check-label">' + escapeHtml(c.label) + badge + '</span>' +
1974 '<span class="mxch-seod-check-detail">' + escapeHtml(c.detail) + '</span>' +
1975 '</div>' +
1976 fixBtn +
1977 '</div>';
1978 });
1979 html += '</div>';
1980
1981 // Optimize All button (only if there are issues)
1982 if (summary.fail > 0 || summary.warn > 0) {
1983 html += '<div class="mxch-seod-detail-actions">' +
1984 '<button type="button" class="mxch-seod-optimize-btn">' +
1985 '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 3-1.9 5.8a2 2 0 0 1-1.287 1.288L3 12l5.8 1.9a2 2 0 0 1 1.288 1.287L12 21l1.9-5.8a2 2 0 0 1 1.287-1.288L21 12l-5.8-1.9a2 2 0 0 1-1.288-1.287Z"/></svg>' +
1986 ' Optimize All' +
1987 '</button>' +
1988 '</div>';
1989 }
1990
1991 $detail.html(html);
1992
1993 // GSC placeholder for free/non-addon users
1994 if (!mxchatContent.hasGSC) {
1995 var badgeLabel = mxchatContent.isActivated ? 'ADD-ON' : 'PRO';
1996 var badgeClass = mxchatContent.isActivated ? '' : ' mxch-seod-pro-badge';
1997 var upgradeText = mxchatContent.isActivated ? 'Install Add-on' : 'Upgrade to Pro';
1998 var gscHtml =
1999 '<div class="mxch-gsc-placeholder">' +
2000 '<h4 class="mxch-gsc-placeholder-title">' +
2001 '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>' +
2002 ' Search Performance' +
2003 '</h4>' +
2004 '<div class="mxch-gsc-placeholder-content">' +
2005 '<div class="mxch-gsc-stats">' +
2006 '<div class="mxch-gsc-stat"><span class="mxch-gsc-stat-value">42</span><span class="mxch-gsc-stat-label">Clicks</span></div>' +
2007 '<div class="mxch-gsc-stat"><span class="mxch-gsc-stat-value">1.2K</span><span class="mxch-gsc-stat-label">Impressions</span></div>' +
2008 '<div class="mxch-gsc-stat"><span class="mxch-gsc-stat-value">3.5%</span><span class="mxch-gsc-stat-label">CTR</span></div>' +
2009 '<div class="mxch-gsc-stat"><span class="mxch-gsc-stat-value">8.2</span><span class="mxch-gsc-stat-label">Avg Position</span></div>' +
2010 '</div>' +
2011 '<table class="mxch-gsc-table">' +
2012 '<thead><tr><th>Keyword</th><th>Clicks</th><th>Impr.</th><th>Position</th></tr></thead>' +
2013 '<tbody>' +
2014 '<tr><td>example keyword one</td><td>18</td><td>420</td><td>5.3</td></tr>' +
2015 '<tr><td>sample search term</td><td>14</td><td>380</td><td>7.1</td></tr>' +
2016 '<tr><td>another query phrase</td><td>10</td><td>290</td><td>12.4</td></tr>' +
2017 '</tbody>' +
2018 '</table>' +
2019 '</div>' +
2020 '<div class="mxch-gsc-placeholder-overlay">' +
2021 '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>' +
2022 '<a href="https://mxchat.ai/" target="_blank" class="mxch-seod-addon-badge' + badgeClass + '">' + badgeLabel + '</a>' +
2023 '<a href="https://mxchat.ai/" target="_blank" class="mxch-cg-pro-upgrade-link">' + upgradeText + '</a>' +
2024 '</div>' +
2025 '</div>';
2026 $detail.append(gscHtml);
2027 }
2028 }
2029
2030 function closeSeoModal() {
2031 seodState.expandedId = null;
2032 var $overlay = $('.mxch-seod-modal-overlay');
2033 if ($overlay.length) {
2034 $overlay.removeClass('mxch-seod-modal-visible');
2035 setTimeout(function() { $overlay.remove(); }, 200);
2036 }
2037 }
2038
2039 function bulkSeoScan() {
2040 seodState.scanning = true;
2041 seodState.scanAborted = false;
2042 var $btn = $('#mxch-seod-scan-all');
2043 var $status = $('#mxch-seod-scan-status');
2044 $btn.hide();
2045
2046 // Show stop button
2047 if (!$('#mxch-seod-scan-stop').length) {
2048 $btn.after('<button type="button" class="mxch-seod-scan-btn mxch-seod-scan-stop-btn" id="mxch-seod-scan-stop"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="2"/></svg> <span>Stop</span></button>');
2049 }
2050 $('#mxch-seod-scan-stop').show();
2051 $status.text('Loading unscored posts...');
2052
2053 // Fetch ALL unscored post IDs across all pages
2054 var allIds = [];
2055 function fetchPage(page) {
2056 $.post(ajaxurl, {
2057 action: 'mxchat_seo_list_posts',
2058 nonce: mxchatContent.nonce,
2059 page: page,
2060 post_type: 'any',
2061 filter: 'unscored',
2062 search: '',
2063 }).done(function(res) {
2064 if (!res.success || !res.data.posts.length) {
2065 if (allIds.length === 0) {
2066 finishScan('All posts have been scanned.');
2067 return;
2068 }
2069 startScanning(allIds);
2070 return;
2071 }
2072 res.data.posts.forEach(function(p) { allIds.push(p.id); });
2073 if (page < res.data.pages) {
2074 $status.text('Loading unscored posts... (' + allIds.length + ' found)');
2075 fetchPage(page + 1);
2076 } else {
2077 startScanning(allIds);
2078 }
2079 }).fail(function() {
2080 finishScan('Error loading posts.');
2081 });
2082 }
2083
2084 function startScanning(ids) {
2085 var total = ids.length;
2086 var scanned = 0;
2087 var batchSize = 10;
2088 $status.html('<span class="mxch-seod-scan-progress">0 / ' + total + '</span>');
2089
2090 function updateRows(results) {
2091 $.each(results, function(pid, data) {
2092 var $row = $('.mxch-seod-row[data-post-id="' + pid + '"]');
2093 if ($row.length) {
2094 var score = data.score;
2095 var cls = score >= 80 ? 'mxch-seod-good' : score >= 50 ? 'mxch-seod-ok' : 'mxch-seod-bad';
2096 $row.find('.mxch-seod-score-badge')
2097 .removeClass('mxch-seod-unscored').addClass(cls).text(score);
2098 }
2099 });
2100 }
2101
2102 function scanNextBatch() {
2103 if (seodState.scanAborted) {
2104 finishScan('Stopped — ' + scanned + ' of ' + total + ' scanned.');
2105 loadSeoPosts();
2106 return;
2107 }
2108 if (scanned >= total) {
2109 finishScan('Done! ' + total + ' posts scanned.');
2110 loadSeoPosts();
2111 return;
2112 }
2113 var batch = ids.slice(scanned, scanned + batchSize);
2114 $status.html('<span class="mxch-seod-scan-progress">' + (scanned + 1) + ' / ' + total + '</span>');
2115 $.post(ajaxurl, {
2116 action: 'mxchat_seo_analyze_batch',
2117 nonce: mxchatContent.nonce,
2118 'post_ids[]': batch,
2119 }).done(function(res) {
2120 if (res.success && res.data.results) {
2121 updateRows(res.data.results);
2122 }
2123 }).always(function() {
2124 scanned += batch.length;
2125 $status.html('<span class="mxch-seod-scan-progress">' + scanned + ' / ' + total + '</span>');
2126 scanNextBatch();
2127 });
2128 }
2129 scanNextBatch();
2130 }
2131
2132 function finishScan(msg) {
2133 seodState.scanning = false;
2134 seodState.scanAborted = false;
2135 $('#mxch-seod-scan-stop').hide();
2136 $btn.show().prop('disabled', false);
2137 $status.text(msg);
2138 }
2139
2140 fetchPage(1);
2141 }
2142
2143 function runSeodOptimize(postId, $btn) {
2144 var origHtml = $btn.html();
2145 $btn.prop('disabled', true).html(
2146 '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 3-1.9 5.8a2 2 0 0 1-1.287 1.288L3 12l5.8 1.9a2 2 0 0 1 1.288 1.287L12 21l1.9-5.8a2 2 0 0 1 1.287-1.288L21 12l-5.8-1.9a2 2 0 0 1-1.288-1.287Z"/></svg>' +
2147 ' Optimizing&hellip;'
2148 );
2149
2150 // Get the current checks to find what needs fixing
2151 $.post(ajaxurl, {
2152 action: 'mxchat_seo_analyze',
2153 nonce: mxchatContent.nonce,
2154 post_id: postId,
2155 }).done(function(res) {
2156 if (!res.success) {
2157 $btn.prop('disabled', false).html(origHtml);
2158 return;
2159 }
2160 var checks = res.data.checks;
2161 var prefs = mxchatContent.seoOptimize || {};
2162 var fields = [];
2163 if (prefs.meta_description !== false && checks.meta_desc && checks.meta_desc.status !== 'pass') fields.push('meta_description');
2164 if (prefs.seo_title !== false && checks.title_length && checks.title_length.status !== 'pass') fields.push('seo_title');
2165 if (prefs.slug !== false && checks.slug && checks.slug.status !== 'pass') fields.push('slug');
2166 // Readability, internal links, images require Advanced Content Editor add-on
2167 if (mxchatContent.hasAdvancedContent) {
2168 if (prefs.readability !== false && checks.readability && checks.readability.status !== 'pass') fields.push('readability');
2169 if (prefs.internal_links !== false && checks.internal_links && checks.internal_links.status !== 'pass') fields.push('internal_links');
2170 if (prefs.img_alt !== false && checks.img_alt && checks.img_alt.status !== 'pass') fields.push('img_alt');
2171 if (prefs.featured_img !== false && checks.featured_img && checks.featured_img.status !== 'pass') fields.push('featured_img');
2172 }
2173 if (!fields.length) fields.push('meta_description');
2174
2175 // Run fields sequentially to avoid race conditions
2176 // (multiple optimizers read/write post_content)
2177 var idx = 0;
2178 function runNext() {
2179 if (idx >= fields.length) {
2180 if (seodState.expandedId === postId) {
2181 openSeoModal(postId);
2182 }
2183 $btn.prop('disabled', false).html(origHtml);
2184 return;
2185 }
2186 $.post(ajaxurl, {
2187 action: 'mxchat_seo_suggest',
2188 nonce: mxchatContent.nonce,
2189 post_id: postId,
2190 field: fields[idx],
2191 }).always(function() {
2192 idx++;
2193 runNext();
2194 });
2195 }
2196 runNext();
2197 }).fail(function() {
2198 $btn.prop('disabled', false).html(origHtml);
2199 });
2200 }
2201
2202 function showNotice(message, type) {
2203 $('.mxch-cg-notice').remove();
2204 var typeClass = type === 'error' ? 'mxch-cg-notice-error' : 'mxch-cg-notice-success';
2205 var $notice = $('<div class="mxch-cg-notice ' + typeClass + '">' + escapeHtml(message) + '</div>');
2206 $('#mxch-cg-inline-form .mxch-cg-form').prepend($notice);
2207 setTimeout(function() { $notice.fadeOut(300, function() { $(this).remove(); }); }, 4000);
2208 }
2209
2210 // ─── Initialize ────────────────────────────────────────────────────
2211
2212 $(document).ready(function() {
2213 initNavigation();
2214 initInlineForm();
2215 initGeneration();
2216 initPromptModal();
2217 initPreview();
2218 initChat();
2219 initSettingsAutoSave();
2220 initLeftTabs();
2221 initSeo();
2222 initSeoSection();
2223 initHistory();
2224 initStatusDropdown();
2225
2226 // Prevent interaction with locked pro feature toggles
2227 $('.mxch-cg-pro-locked .mxch-toggle-input').on('click', function(e) {
2228 e.preventDefault();
2229 return false;
2230 });
2231 });
2232
2233 })(jQuery);
2234