PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.2
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.2
2.7.1 2.7.2 2.7.0 2.6.26 2.6.25 2.6.24 2.6.23 2.6.22 2.6.21 2.6.20 2.6.19 2.6.18 2.6.17 2.6.16 2.6.15 2.6.14 2.6.13 2.6.12 2.6.11 2.6.10 2.6.9 2.6.8 2.6.7 2.6.6 2.6.5 All 141 releases
metasync / admin / js / metasync-excluded-urls.js

metasync-excluded-urls.js in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.7.2, at admin/js/metasync-excluded-urls.js

337 lines 14.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* global metasyncExcludedUrlsData, jQuery */
2 /**
3 * MetaSync OTTO Excluded URLs Management
4 *
5 * Extracted for Phase 5, #887.
6 * Handles adding, listing, rechecking, and deleting excluded URLs.
7 *
8 * Localized object: metasyncExcludedUrlsData
9 * - ajaxUrl (string)
10 * - nonce (string)
11 *
12 * @since Phase 5
13 */
14 (function () {
15 if (typeof jQuery === 'undefined') {
16 setTimeout(arguments.callee, 100);
17 return;
18 }
19
20 jQuery(document).ready(function ($) {
21 var currentPage = 1;
22 var perPage = 10;
23 var ajaxUrl = metasyncExcludedUrlsData.ajaxUrl;
24 var nonce = metasyncExcludedUrlsData.nonce;
25
26 // Load excluded URLs on page load
27 loadExcludedURLs();
28
29 // Add excluded URL button click
30 $('#otto-add-excluded-url-btn').on('click', function (e) {
31 e.preventDefault();
32 addExcludedURL();
33 });
34
35 // Handle Enter key in URL pattern input
36 $('#otto-url-pattern').on('keypress', function (e) {
37 if (e.which === 13) {
38 e.preventDefault();
39 addExcludedURL();
40 }
41 });
42
43 function addExcludedURL() {
44 var $button = $('#otto-add-excluded-url-btn');
45 var $status = $('#otto-add-status');
46 var urlPattern = $('#otto-url-pattern').val().trim();
47 var patternType = $('#otto-pattern-type').val();
48 var description = $('#otto-description').val().trim();
49
50 // Validate inputs
51 if (!urlPattern) {
52 showStatus('error', 'URL pattern is required');
53 return;
54 }
55
56 // Disable button and show loading
57 $button.prop('disabled', true).text('Adding...');
58 $status.hide();
59
60 $.ajax({
61 url: ajaxUrl,
62 type: 'POST',
63 data: {
64 action: 'metasync_otto_add_excluded_url',
65 nonce: nonce,
66 url_pattern: urlPattern,
67 pattern_type: patternType,
68 description: description
69 },
70 success: function (response) {
71 if (response.success) {
72 showStatus('success', response.data.message || 'URL excluded successfully');
73 // Clear form
74 $('#otto-url-pattern').val('');
75 $('#otto-description').val('');
76 $('#otto-pattern-type').val('exact');
77 // Reload list
78 loadExcludedURLs();
79 } else {
80 showStatus('error', response.data.message || 'Failed to add excluded URL');
81 }
82 },
83 error: function () {
84 showStatus('error', 'An error occurred. Please try again.');
85 },
86 complete: function () {
87 $button.prop('disabled', false).text('\u2795 Add Excluded URL');
88 }
89 });
90 }
91
92 function loadExcludedURLs(page) {
93 page = page || currentPage;
94 var $container = $('#otto-excluded-urls-table-container');
95
96 $container.html('<div class="otto-table-message"><p>Loading...</p></div>');
97
98 $.ajax({
99 url: ajaxUrl,
100 type: 'POST',
101 data: {
102 action: 'metasync_otto_get_excluded_urls',
103 nonce: nonce,
104 page: page,
105 per_page: perPage
106 },
107 success: function (response) {
108 if (response.success) {
109 // Page emptied (e.g. last record on it was deleted): fall
110 // back to the previous page instead of showing the
111 // empty-state message while records still exist.
112 if (page > 1 && (!response.data.records || response.data.records.length === 0)) {
113 loadExcludedURLs(page - 1);
114 return;
115 }
116 currentPage = page;
117 renderExcludedURLsTable(response.data.records, response.data.pagination);
118 } else {
119 $container.html('<div class="otto-table-message otto-error-message"><p>Error loading excluded URLs</p></div>');
120 }
121 },
122 error: function () {
123 $container.html('<div class="otto-table-message otto-error-message"><p>Failed to load excluded URLs</p></div>');
124 }
125 });
126 }
127
128 function renderExcludedURLsTable(records, pagination) {
129 var $container = $('#otto-excluded-urls-table-container');
130
131 if (!records || records.length === 0) {
132 $container.html('<div class="otto-table-message"><p>No excluded URLs found. Add one above to get started.</p></div>');
133 return;
134 }
135
136 var html = '<table class="wp-list-table widefat fixed striped" style="margin-top: 10px;">';
137 html += '<thead><tr>';
138 html += '<th style="width: 30%;">URL Pattern</th>';
139 html += '<th style="width: 12%;">Match Type</th>';
140 html += '<th style="width: 25%;">Description</th>';
141 html += '<th style="width: 13%;">Recheck After</th>';
142 html += '<th style="width: 20%;">Actions</th>';
143 html += '</tr></thead><tbody>';
144
145 records.forEach(function (record) {
146 html += '<tr>';
147 html += '<td><code>' + escapeHtml(record.url_pattern) + '</code></td>';
148 html += '<td><span class="otto-pattern-type-badge otto-pattern-' + record.pattern_type + '">' + formatPatternType(record.pattern_type) + '</span></td>';
149 html += '<td>' + (record.description ? escapeHtml(record.description) : '<span class="otto-muted">\u2014</span>') + '</td>';
150 html += '<td>' + formatRecheckAfter(record) + '</td>';
151 html += '<td><span class="otto-actions">';
152 if (record.pattern_type === 'exact') {
153 html += '<button type="button" class="button button-small otto-recheck-url" data-id="' + record.id + '" style="margin-right: 5px;">\uD83D\uDD04 Recheck</button>';
154 } else {
155 // Pattern rows match many URLs, so a single probe would be meaningless.
156 // The otto-recheck-disabled class mirrors the active button's
157 // sizing so the grayed-out button keeps the same width/height as
158 // the enabled one on Exact match rows.
159 html += '<button type="button" class="button button-small otto-recheck-disabled" disabled title="Only exact URL exclusions can be rechecked" style="margin-right: 5px;">\uD83D\uDD04 Recheck</button>';
160 }
161 html += '<button type="button" class="button button-small otto-delete-url" data-id="' + record.id + '">\uD83D\uDDD1\uFE0F Delete</button>';
162 html += '</span></td>';
163 html += '</tr>';
164 });
165
166 html += '</tbody></table>';
167
168 // Add pagination
169 if (pagination.total_pages > 1) {
170 html += '<div class="tablenav" style="margin-top: 15px; display: flex; justify-content: space-between; align-items: center;">';
171 html += '<div class="tablenav-pages">';
172 html += '<span class="displaying-num">' + pagination.total_count + ' items</span>';
173
174 html += '<span class="pagination-links">';
175
176 // First page
177 if (pagination.current_page > 1) {
178 html += '<a class="button otto-page-nav" data-page="1" href="#">\u00AB</a> ';
179 html += '<a class="button otto-page-nav" data-page="' + (pagination.current_page - 1) + '" href="#">\u2039</a> ';
180 } else {
181 html += '<span class="button disabled">\u00AB</span> ';
182 html += '<span class="button disabled">\u2039</span> ';
183 }
184
185 html += '<span class="paging-input">';
186 html += '<span class="tablenav-paging-text">' + pagination.current_page + ' of <span class="total-pages">' + pagination.total_pages + '</span></span>';
187 html += '</span> ';
188
189 // Next/Last page
190 if (pagination.current_page < pagination.total_pages) {
191 html += '<a class="button otto-page-nav" data-page="' + (pagination.current_page + 1) + '" href="#">\u203A</a> ';
192 html += '<a class="button otto-page-nav" data-page="' + pagination.total_pages + '" href="#">\u00BB</a>';
193 } else {
194 html += '<span class="button disabled">\u203A</span> ';
195 html += '<span class="button disabled">\u00BB</span>';
196 }
197
198 html += '</span></div></div>';
199 }
200
201 $container.html(html);
202
203 // Attach delete button handlers
204 $('.otto-delete-url').on('click', function (e) {
205 e.preventDefault();
206 var id = $(this).data('id');
207 deleteExcludedURL(id);
208 });
209
210 // Attach recheck button handlers
211 $('.otto-recheck-url').on('click', function (e) {
212 e.preventDefault();
213 var $btn = $(this);
214 var id = $btn.data('id');
215 recheckExcludedURL(id, $btn);
216 });
217
218 // Attach pagination handlers
219 $('.otto-page-nav').on('click', function (e) {
220 e.preventDefault();
221 var page = $(this).data('page');
222 loadExcludedURLs(page);
223 });
224 }
225
226 // Recheck probes the URL over HTTP and only reports what came back.
227 // Removing the entry is the Delete button's job, so this must never
228 // fall through into a delete.
229 function recheckExcludedURL(id, $btn) {
230 var originalText = $btn.text();
231 $btn.prop('disabled', true).text('Checking...');
232
233 $.ajax({
234 url: ajaxUrl,
235 type: 'POST',
236 data: {
237 action: 'metasync_otto_recheck_excluded_url',
238 nonce: nonce,
239 id: id
240 },
241 success: function (response) {
242 $btn.prop('disabled', false).text(originalText);
243 if (response.success) {
244 if (!response.data.checked) {
245 alert('Could not check this URL: ' + (response.data.message || 'request failed') + '. The site may block loopback requests.');
246 } else if (response.data.available) {
247 alert('This URL is reachable again (HTTP ' + response.data.status + '). It stays on the excluded list; use Delete on this row to remove the exclusion.');
248 } else if (response.data.status === 404 || response.data.status === 410) {
249 alert('This URL is still returning HTTP ' + response.data.status + '.');
250 } else {
251 alert('This URL returned HTTP ' + response.data.status + ', so it may not be reachable for crawlers.');
252 }
253 } else {
254 alert('Error: ' + (response.data.message || 'Recheck failed'));
255 }
256 },
257 error: function (xhr, status, err) {
258 $btn.prop('disabled', false).text(originalText);
259 alert('An error occurred while rechecking. Please try again.');
260 }
261 });
262 }
263
264 function deleteExcludedURL(id) {
265 if (!confirm('Are you sure you want to delete this excluded URL?')) {
266 return;
267 }
268
269 $.ajax({
270 url: ajaxUrl,
271 type: 'POST',
272 data: {
273 action: 'metasync_otto_delete_excluded_url',
274 nonce: nonce,
275 id: id
276 },
277 success: function (response) {
278 if (response.success) {
279 loadExcludedURLs();
280 } else {
281 alert('Error: ' + (response.data.message || 'Failed to delete'));
282 }
283 },
284 error: function () {
285 alert('An error occurred. Please try again.');
286 }
287 });
288 }
289
290 function showStatus(type, message) {
291 var $status = $('#otto-add-status');
292 $status.removeClass('otto-status-success otto-status-error');
293 $status.addClass('otto-status-' + type);
294 $status.text(message).fadeIn();
295
296 setTimeout(function () {
297 $status.fadeOut();
298 }, 5000);
299 }
300
301 function formatPatternType(type) {
302 var types = {
303 'exact': 'Exact',
304 'contain': 'Contains',
305 'start': 'Starts With',
306 'end': 'Ends With',
307 'regex': 'Regex'
308 };
309 return types[type] || type;
310 }
311
312 function formatRecheckAfter(record) {
313 if (!record.auto_excluded || !record.recheck_after) {
314 return '<span class="otto-muted">NA</span>';
315 }
316 var d = new Date(record.recheck_after.replace(' ', 'T'));
317 if (isNaN(d.getTime())) {
318 return escapeHtml(record.recheck_after);
319 }
320 var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
321 return months[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear();
322 }
323
324 function escapeHtml(text) {
325 if (!text) return '';
326 var map = {
327 '&': '&amp;',
328 '<': '&lt;',
329 '>': '&gt;',
330 '"': '&quot;',
331 "'": '&#039;'
332 };
333 return String(text).replace(/[&<>"']/g, function (m) { return map[m]; });
334 }
335 });
336 })();
337