PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.1.1
MxChat – AI Chatbot & Content Generation for WordPress v3.1.1
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
← All changes | js/mxchat-content.js +27 -272 3.2.213.1.1 View file →
@@ -27,9 +27,8 @@
27 27 historyLoading: false,
28 28 phraseTimer: null,
29 29 phraseIndex: 0,
30 30 pollStartTime: null,
31 - customSystemPrompt: '',
32 31 lastProgressTime: null,
33 32 postStatus: null
34 33 };
35 34
@@ -180,91 +179,8 @@
180 179 startGeneration();
181 180 });
182 181 }
183 182
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 183 function startGeneration() {
268 184 var prompt = $('#mxch-cg-prompt').val().trim();
269 185 if (!prompt) {
270 186 showNotice('Please enter a prompt describing the content you want to generate.', 'error');
@@ -292,11 +208,9 @@
292 208 content_type: $('#mxch-cg-type').val(),
293 209 post_status: $('#mxch-cg-status').val(),
294 210 schedule_date: $('#mxch-cg-schedule').val() || '',
295 211 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 || ''
212 + title_display: $('#mxch-cg-title-display').val() || 'hide'
299 213 };
300 214
301 215 $.ajax({
302 216 url: mxchatContent.ajaxUrl,
@@ -948,15 +862,8 @@
948 862 });
949 863 $('#mxch-seo-ai-optimize').on('click', function() {
950 864 if (state.postId && !seoState.fixing) runAiOptimize();
951 865 });
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 866 // Auto-analyze when switching to SEO tab if content exists
960 867 $(document).on('click', '.mxch-cg-left-tab[data-tab="seo"]', function() {
961 868 if (state.postId && !seoState.analyzed && !seoState.analyzing) runSeoAnalysis();
962 869 });
@@ -1011,11 +918,8 @@
1011 918 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 919 };
1013 920 // Checks that require the Advanced Content Editor add-on to fix
1014 921 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 922 var sorted = Object.keys(checks).sort(function(a, b) {
1019 923 var o = { fail: 0, warn: 1, pass: 2 };
1020 924 return (o[checks[a].status] || 2) - (o[checks[b].status] || 2);
1021 925 });
@@ -1036,17 +940,8 @@
1036 940 badge = ' <a href="https://mxchat.ai/" target="_blank" class="mxch-seod-addon-badge mxch-seod-pro-badge">PRO</a>';
1037 941 }
1038 942 }
1039 943
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 944 $list.append(
1050 945 '<div class="mxch-seo-check" data-check="' + key + '">' +
1051 946 '<div class="mxch-seo-check-icon mxch-seo-' + c.status + '">' + icons[c.status] + '</div>' +
1052 947 '<div class="mxch-seo-check-content">' +
@@ -1052,9 +947,8 @@
1052 947 '<div class="mxch-seo-check-content">' +
1053 948 '<span class="mxch-seo-check-label">' + escapeHtml(c.label) + badge + '</span>' +
1054 949 '<span class="mxch-seo-check-detail">' + escapeHtml(c.detail) + '</span>' +
1055 950 '</div>' +
1056 - fixBtn +
1057 951 '</div>'
1058 952 );
1059 953 });
1060 954 $('#mxch-seo-actions').toggle(summary.fail > 0 || summary.warn > 0);
@@ -1063,28 +957,8 @@
1063 957 function renderSeoError(msg) {
1064 958 $('#mxch-seo-checklist').html('<div class="mxch-seo-empty"><span style="color:#ef4444;">' + escapeHtml(msg) + '</span></div>');
1065 959 }
1066 960
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 961 function runAiOptimize() {
1088 962 if (!state.postId || seoState.fixing) return;
1089 963 seoState.fixing = true;
1090 964 var $btn = $('#mxch-seo-ai-optimize'), origHtml = $btn.html();
@@ -1095,28 +969,12 @@
1095 969 if (seoState.checks) {
1096 970 if (prefs.meta_description !== false && seoState.checks.meta_desc && seoState.checks.meta_desc.status !== 'pass') fields.push('meta_description');
1097 971 if (prefs.seo_title !== false && seoState.checks.title_length && seoState.checks.title_length.status !== 'pass') fields.push('seo_title');
1098 972 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 973 }
1107 974 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];
975 + var done = 0;
976 + fields.forEach(function(field) {
1119 977 $.post(ajaxurl, {
1120 978 action: 'mxchat_seo_suggest',
1121 979 nonce: mxchatContent.nonce,
1122 980 post_id: state.postId,
@@ -1129,13 +987,15 @@
1129 987 $('.mxch-cg-left-tab[data-tab="meta"]').addClass('mxch-cg-tab-flash');
1130 988 setTimeout(function() { $('.mxch-cg-left-tab[data-tab="meta"]').removeClass('mxch-cg-tab-flash'); }, 2000);
1131 989 }
1132 990 }).always(function() {
1133 - idx++;
1134 - runNext();
991 + if (++done >= fields.length) {
992 + seoState.fixing = false;
993 + $btn.removeClass('mxch-seo-fixing').prop('disabled', false).html(origHtml);
994 + runSeoAnalysis();
995 + }
1135 996 });
1136 - }
1137 - runNext();
997 + });
1138 998 }
1139 999
1140 1000 function resetSeoPanel() {
1141 1001 seoState = { analyzed: false, analyzing: false, fixing: false, score: null, checks: null };
@@ -1656,15 +1516,8 @@
1656 1516 seodState.scanAborted = true;
1657 1517 $(this).prop('disabled', true).find('span').text('Stopping...');
1658 1518 });
1659 1519
1660 - // "Connect Search Console" hint row → jump to the Settings section,
1661 - // where the add-on's GSC card lives (eadc60).
1662 - $(document).on('click', '.mxch-seod-gsc-hint-link', function(e) {
1663 - e.preventDefault();
1664 - $('.mxch-nav-link[data-target="content-settings"]').trigger('click');
1665 - });
1666 -
1667 1520 // Sortable column headers (all columns sort server-side)
1668 1521 $(document).on('click', '.mxch-seod-header-cell[data-sort]', function() {
1669 1522 var col = $(this).data('sort');
1670 1523 if (seodState.sortBy === col) {
@@ -1682,65 +1535,10 @@
1682 1535 e.stopPropagation();
1683 1536 var postId = $(this).closest('.mxch-seod-detail').data('post-id');
1684 1537 runSeodOptimize(postId, $(this));
1685 1538 });
1686 -
1687 - // Per-check AI fix buttons in detail modal
1688 - $(document).on('click', '.mxch-seod-check-fix-btn', function(e) {
1689 - e.stopPropagation();
1690 - var $btn = $(this);
1691 - if ($btn.hasClass('mxch-seo-check-fixing')) return;
1692 - var field = $btn.data('field');
1693 - var postId = $btn.data('post-id');
1694 - $btn.addClass('mxch-seo-check-fixing').prop('disabled', true);
1695 - $.post(ajaxurl, {
1696 - action: 'mxchat_seo_suggest',
1697 - nonce: mxchatContent.nonce,
1698 - post_id: postId,
1699 - field: field,
1700 - }).always(function() {
1701 - $btn.removeClass('mxch-seo-check-fixing').prop('disabled', false);
1702 - if (seodState.expandedId === postId) {
1703 - openSeoModal(postId);
1704 - }
1705 - });
1706 - });
1707 -
1708 - // Checkbox: prevent row click when clicking checkbox
1709 - $(document).on('click', '.mxch-seod-cell-check, .mxch-seod-header-check', function(e) {
1710 - e.stopPropagation();
1711 - });
1712 -
1713 - // Select all checkbox
1714 - $(document).on('change', '.mxch-seod-check-all', function() {
1715 - var checked = $(this).prop('checked');
1716 - $('.mxch-seod-row-check').prop('checked', checked);
1717 - updateOptimizeSelectedBtn();
1718 - });
1719 -
1720 - // Individual row checkbox
1721 - $(document).on('change', '.mxch-seod-row-check', function() {
1722 - var allChecked = $('.mxch-seod-row-check').length === $('.mxch-seod-row-check:checked').length;
1723 - $('.mxch-seod-check-all').prop('checked', allChecked);
1724 - updateOptimizeSelectedBtn();
1725 - });
1726 1539 }
1727 1540
1728 - function updateOptimizeSelectedBtn() {
1729 - var count = $('.mxch-seod-row-check:checked').length;
1730 - var $btn = $('#mxch-seod-optimize-selected');
1731 - var $note = $('#mxch-seod-bulk-note');
1732 - var isLocked = $btn.hasClass('mxch-seod-bulk-locked');
1733 - if (count > 0) {
1734 - $btn.find('span').first().text(isLocked ? 'Bulk Optimize' : 'Optimize Selected (' + count + ')');
1735 - $btn.show();
1736 - if (isLocked) $note.show();
1737 - } else {
1738 - $btn.hide();
1739 - $note.hide();
1740 - }
1741 - }
1742 -
1743 1541 function loadSeoPosts() {
1744 1542 seodState.loading = true;
1745 1543 $('#mxch-seod-loading').show();
1746 1544 $('#mxch-seod-empty').hide();
@@ -1792,39 +1590,20 @@
1792 1590 var $table = $('#mxch-seod-table');
1793 1591 $table.empty();
1794 1592 seodState.expandedId = null;
1795 1593
1796 - // Column headers.
1797 - // gscUnlinked: the add-on unlocked the GSC columns, but Search Console
1798 - // itself isn't linked yet — every cell is a dash, so sorting Clicks /
1799 - // Impr. is noise. Keep the unlocked look, drop data-sort, and show one
1800 - // slim hint row pointing at the Settings tab (eadc60). When linked,
1801 - // rendering below is byte-identical to before.
1802 - var gscUnlinked = mxchatContent.hasGSC && !mxchatContent.gscLinked;
1803 - var gscSortable = mxchatContent.hasGSC && !gscUnlinked;
1594 + // Column headers
1804 1595 var activeClass = function(col) { return seodState.sortBy === col ? ' mxch-seod-header-active' : ''; };
1805 1596 $table.append(
1806 1597 '<div class="mxch-seod-header">' +
1807 - '<div class="mxch-seod-header-cell mxch-seod-header-check"><input type="checkbox" class="mxch-seod-check-all" title="Select all"></div>' +
1808 1598 '<div class="mxch-seod-header-cell mxch-seod-header-title' + activeClass('title') + '" data-sort="title">Title' + seodSortArrow('title') + '</div>' +
1809 1599 '<div class="mxch-seod-header-cell mxch-seod-header-date' + activeClass('date') + '" data-sort="date">Date' + seodSortArrow('date') + '</div>' +
1810 1600 '<div class="mxch-seod-header-cell mxch-seod-header-score' + activeClass('score') + '" data-sort="score">Score' + seodSortArrow('score') + '</div>' +
1811 - '<div class="mxch-seod-header-cell mxch-seod-header-clicks' + (!mxchatContent.hasGSC ? ' mxch-seod-header-locked' : '') + '"' + (gscSortable ? ' data-sort="clicks"' : '') + '>Clicks' + (gscSortable ? seodSortArrow('clicks') : '') + (!mxchatContent.hasGSC ? ' <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>' +
1812 - '<div class="mxch-seod-header-cell mxch-seod-header-impressions' + (!mxchatContent.hasGSC ? ' mxch-seod-header-locked' : '') + '"' + (gscSortable ? ' data-sort="impressions"' : '') + '>Impr.' + (gscSortable ? seodSortArrow('impressions') : '') + (!mxchatContent.hasGSC ? ' <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>' +
1813 - '<div class="mxch-seod-header-cell mxch-seod-header-position' + (!mxchatContent.hasGSC ? ' mxch-seod-header-locked' : '') + '" title="Average search position, last 28 days">Pos.' + (!mxchatContent.hasGSC ? ' <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>' +
1601 + '<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>' +
1602 + '<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>' +
1814 1603 '</div>'
1815 1604 );
1816 1605
1817 - if (gscUnlinked) {
1818 - $table.append(
1819 - '<div class="mxch-seod-gsc-hint">' +
1820 - '<span class="mxch-seod-gsc-hint-dot"></span>' +
1821 - '<span>Connect Google Search Console to fill the Clicks, Impressions and Position columns &mdash; ' +
1822 - '<a href="#" class="mxch-seod-gsc-hint-link">open Settings</a></span>' +
1823 - '</div>'
1824 - );
1825 - }
1826 -
1827 1606 posts.forEach(function(p) {
1828 1607 var scoreHtml;
1829 1608 if (p.score !== null) {
1830 1609 var cls = p.score >= 80 ? 'mxch-seod-good' : p.score >= 50 ? 'mxch-seod-ok' : 'mxch-seod-bad';
@@ -1832,14 +1611,13 @@
1832 1611 } else {
1833 1612 scoreHtml = '<div class="mxch-seod-score-badge mxch-seod-unscored">&mdash;</div>';
1834 1613 }
1835 1614
1836 - var typeLabel = p.type.charAt(0).toUpperCase() + p.type.slice(1);
1615 + var typeLabel = p.type === 'page' ? 'Page' : 'Post';
1837 1616
1838 1617 $table.append(
1839 - '<div class="mxch-seod-row" data-post-id="' + p.id + '" data-score="' + (p.score !== null ? p.score : -1) + '" data-permalink="' + escapeAttr(p.permalink) + '">' +
1618 + '<div class="mxch-seod-row" data-post-id="' + p.id + '" data-score="' + (p.score !== null ? p.score : -1) + '">' +
1840 1619 '<div class="mxch-seod-row-main">' +
1841 - '<div class="mxch-seod-cell mxch-seod-cell-check"><input type="checkbox" class="mxch-seod-row-check" data-post-id="' + p.id + '"></div>' +
1842 1620 '<div class="mxch-seod-cell mxch-seod-cell-title">' +
1843 1621 '<span class="mxch-seod-title">' + escapeHtml(p.title) + '</span>' +
1844 1622 '<span class="mxch-seod-meta">' + typeLabel + '</span>' +
1845 1623 '</div>' +
@@ -1848,9 +1626,8 @@
1848 1626 '</div>' +
1849 1627 '<div class="mxch-seod-cell mxch-seod-cell-score">' + scoreHtml + '</div>' +
1850 1628 '<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>' +
1851 1629 '<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>' +
1852 - '<div class="mxch-seod-cell mxch-seod-cell-position' + (!mxchatContent.hasGSC ? ' mxch-seod-cell-locked' : '') + '">&mdash;</div>' +
1853 1630 '</div>' +
1854 1631 '</div>'
1855 1632 );
1856 1633 });
@@ -1888,9 +1665,8 @@
1888 1665 seodState.expandedId = postId;
1889 1666
1890 1667 var $row = $('.mxch-seod-row[data-post-id="' + postId + '"]');
1891 1668 var title = $row.find('.mxch-seod-title').text() || 'Post #' + postId;
1892 - var permalink = $row.attr('data-permalink') || '';
1893 1669
1894 1670 var $overlay = $(
1895 1671 '<div class="mxch-seod-modal-overlay">' +
1896 1672 '<div class="mxch-seod-modal">' +
@@ -1895,11 +1671,8 @@
1895 1671 '<div class="mxch-seod-modal-overlay">' +
1896 1672 '<div class="mxch-seod-modal">' +
1897 1673 '<div class="mxch-seod-modal-header">' +
1898 1674 '<h3 class="mxch-seod-modal-title">' + escapeHtml(title) + '</h3>' +
1899 - (permalink ? '<a href="' + escapeAttr(permalink) + '" target="_blank" class="mxch-seod-modal-view-page" title="View Page">' +
1900 - '<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>' +
1901 - ' View Page</a>' : '') +
1902 1675 '<button type="button" class="mxch-seod-modal-close" title="Close">' +
1903 1676 '<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>' +
1904 1677 '</button>' +
1905 1678 '</div>' +
@@ -1956,10 +1729,8 @@
1956 1729 };
1957 1730
1958 1731 // Checks that require the Advanced Content Editor add-on to fix
1959 1732 var addonChecks = { readability: true, internal_links: true, img_alt: true, featured_img: true };
1960 - 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' };
1961 - 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>';
1962 1733
1963 1734 var sorted = Object.keys(checks).sort(function(a, b) {
1964 1735 var o = { fail: 0, warn: 1, pass: 2 };
1965 1736 return (o[checks[a].status] || 2) - (o[checks[b].status] || 2);
@@ -1983,17 +1754,8 @@
1983 1754 badge = ' <a href="https://mxchat.ai/" target="_blank" class="mxch-seod-addon-badge mxch-seod-pro-badge">PRO</a>';
1984 1755 }
1985 1756 }
1986 1757
1987 - // Per-check AI fix button
1988 - var fixBtn = '';
1989 - if (c.status !== 'pass' && fixableMap[key]) {
1990 - var canFix = !addonChecks[key] || mxchatContent.hasAdvancedContent;
1991 - if (canFix) {
1992 - 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>';
1993 - }
1994 - }
1995 -
1996 1758 html += '<div class="mxch-seod-check mxch-seod-check-' + c.status + '">' +
1997 1759 '<div class="mxch-seod-check-icon">' + icons[c.status] + '</div>' +
1998 1760 '<div class="mxch-seod-check-text">' +
1999 1761 '<span class="mxch-seod-check-label">' + escapeHtml(c.label) + badge + '</span>' +
@@ -1998,19 +1760,18 @@
1998 1760 '<div class="mxch-seod-check-text">' +
1999 1761 '<span class="mxch-seod-check-label">' + escapeHtml(c.label) + badge + '</span>' +
2000 1762 '<span class="mxch-seod-check-detail">' + escapeHtml(c.detail) + '</span>' +
2001 1763 '</div>' +
2002 - fixBtn +
2003 1764 '</div>';
2004 1765 });
2005 1766 html += '</div>';
2006 1767
2007 - // Optimize All button (only if there are issues)
1768 + // AI Optimize button (only if there are issues)
2008 1769 if (summary.fail > 0 || summary.warn > 0) {
2009 1770 html += '<div class="mxch-seod-detail-actions">' +
2010 1771 '<button type="button" class="mxch-seod-optimize-btn">' +
2011 1772 '<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>' +
2012 - ' Optimize All' +
1773 + ' AI Optimize' +
2013 1774 '</button>' +
2014 1775 '</div>';
2015 1776 }
2016 1777
@@ -2197,30 +1958,25 @@
2197 1958 if (prefs.featured_img !== false && checks.featured_img && checks.featured_img.status !== 'pass') fields.push('featured_img');
2198 1959 }
2199 1960 if (!fields.length) fields.push('meta_description');
2200 1961
2201 - // Run fields sequentially to avoid race conditions
2202 - // (multiple optimizers read/write post_content)
2203 - var idx = 0;
2204 - function runNext() {
2205 - if (idx >= fields.length) {
2206 - if (seodState.expandedId === postId) {
2207 - openSeoModal(postId);
2208 - }
2209 - $btn.prop('disabled', false).html(origHtml);
2210 - return;
2211 - }
1962 + var done = 0;
1963 + fields.forEach(function(field) {
2212 1964 $.post(ajaxurl, {
2213 1965 action: 'mxchat_seo_suggest',
2214 1966 nonce: mxchatContent.nonce,
2215 1967 post_id: postId,
2216 - field: fields[idx],
1968 + field: field,
2217 1969 }).always(function() {
2218 - idx++;
2219 - runNext();
1970 + if (++done >= fields.length) {
1971 + // Re-analyze and re-render in modal
1972 + if (seodState.expandedId === postId) {
1973 + openSeoModal(postId);
1974 + }
1975 + $btn.prop('disabled', false).html(origHtml);
1976 + }
2220 1977 });
2221 - }
2222 - runNext();
1978 + });
2223 1979 }).fail(function() {
2224 1980 $btn.prop('disabled', false).html(origHtml);
2225 1981 });
2226 1982 }
@@ -2238,9 +1994,8 @@
2238 1994 $(document).ready(function() {
2239 1995 initNavigation();
2240 1996 initInlineForm();
2241 1997 initGeneration();
2242 - initPromptModal();
2243 1998 initPreview();
2244 1999 initChat();
2245 2000 initSettingsAutoSave();
2246 2001 initLeftTabs();