PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.7.1
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.7.1
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.1, at admin/js/metasync-excluded-urls.js

334 lines 13.9 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 html += '<button type="button" class="button button-small" disabled title="Only exact URL exclusions can be rechecked" style="margin-right: 5px;">\uD83D\uDD04 Recheck</button>';
157 }
158 html += '<button type="button" class="button button-small otto-delete-url" data-id="' + record.id + '">\uD83D\uDDD1\uFE0F Delete</button>';
159 html += '</span></td>';
160 html += '</tr>';
161 });
162
163 html += '</tbody></table>';
164
165 // Add pagination
166 if (pagination.total_pages > 1) {
167 html += '<div class="tablenav" style="margin-top: 15px; display: flex; justify-content: space-between; align-items: center;">';
168 html += '<div class="tablenav-pages">';
169 html += '<span class="displaying-num">' + pagination.total_count + ' items</span>';
170
171 html += '<span class="pagination-links">';
172
173 // First page
174 if (pagination.current_page > 1) {
175 html += '<a class="button otto-page-nav" data-page="1" href="#">\u00AB</a> ';
176 html += '<a class="button otto-page-nav" data-page="' + (pagination.current_page - 1) + '" href="#">\u2039</a> ';
177 } else {
178 html += '<span class="button disabled">\u00AB</span> ';
179 html += '<span class="button disabled">\u2039</span> ';
180 }
181
182 html += '<span class="paging-input">';
183 html += '<span class="tablenav-paging-text">' + pagination.current_page + ' of <span class="total-pages">' + pagination.total_pages + '</span></span>';
184 html += '</span> ';
185
186 // Next/Last page
187 if (pagination.current_page < pagination.total_pages) {
188 html += '<a class="button otto-page-nav" data-page="' + (pagination.current_page + 1) + '" href="#">\u203A</a> ';
189 html += '<a class="button otto-page-nav" data-page="' + pagination.total_pages + '" href="#">\u00BB</a>';
190 } else {
191 html += '<span class="button disabled">\u203A</span> ';
192 html += '<span class="button disabled">\u00BB</span>';
193 }
194
195 html += '</span></div></div>';
196 }
197
198 $container.html(html);
199
200 // Attach delete button handlers
201 $('.otto-delete-url').on('click', function (e) {
202 e.preventDefault();
203 var id = $(this).data('id');
204 deleteExcludedURL(id);
205 });
206
207 // Attach recheck button handlers
208 $('.otto-recheck-url').on('click', function (e) {
209 e.preventDefault();
210 var $btn = $(this);
211 var id = $btn.data('id');
212 recheckExcludedURL(id, $btn);
213 });
214
215 // Attach pagination handlers
216 $('.otto-page-nav').on('click', function (e) {
217 e.preventDefault();
218 var page = $(this).data('page');
219 loadExcludedURLs(page);
220 });
221 }
222
223 // Recheck probes the URL over HTTP and only reports what came back.
224 // Removing the entry is the Delete button's job, so this must never
225 // fall through into a delete.
226 function recheckExcludedURL(id, $btn) {
227 var originalText = $btn.text();
228 $btn.prop('disabled', true).text('Checking...');
229
230 $.ajax({
231 url: ajaxUrl,
232 type: 'POST',
233 data: {
234 action: 'metasync_otto_recheck_excluded_url',
235 nonce: nonce,
236 id: id
237 },
238 success: function (response) {
239 $btn.prop('disabled', false).text(originalText);
240 if (response.success) {
241 if (!response.data.checked) {
242 alert('Could not check this URL: ' + (response.data.message || 'request failed') + '. The site may block loopback requests.');
243 } else if (response.data.available) {
244 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.');
245 } else if (response.data.status === 404 || response.data.status === 410) {
246 alert('This URL is still returning HTTP ' + response.data.status + '.');
247 } else {
248 alert('This URL returned HTTP ' + response.data.status + ', so it may not be reachable for crawlers.');
249 }
250 } else {
251 alert('Error: ' + (response.data.message || 'Recheck failed'));
252 }
253 },
254 error: function (xhr, status, err) {
255 $btn.prop('disabled', false).text(originalText);
256 alert('An error occurred while rechecking. Please try again.');
257 }
258 });
259 }
260
261 function deleteExcludedURL(id) {
262 if (!confirm('Are you sure you want to delete this excluded URL?')) {
263 return;
264 }
265
266 $.ajax({
267 url: ajaxUrl,
268 type: 'POST',
269 data: {
270 action: 'metasync_otto_delete_excluded_url',
271 nonce: nonce,
272 id: id
273 },
274 success: function (response) {
275 if (response.success) {
276 loadExcludedURLs();
277 } else {
278 alert('Error: ' + (response.data.message || 'Failed to delete'));
279 }
280 },
281 error: function () {
282 alert('An error occurred. Please try again.');
283 }
284 });
285 }
286
287 function showStatus(type, message) {
288 var $status = $('#otto-add-status');
289 $status.removeClass('otto-status-success otto-status-error');
290 $status.addClass('otto-status-' + type);
291 $status.text(message).fadeIn();
292
293 setTimeout(function () {
294 $status.fadeOut();
295 }, 5000);
296 }
297
298 function formatPatternType(type) {
299 var types = {
300 'exact': 'Exact',
301 'contain': 'Contains',
302 'start': 'Starts With',
303 'end': 'Ends With',
304 'regex': 'Regex'
305 };
306 return types[type] || type;
307 }
308
309 function formatRecheckAfter(record) {
310 if (!record.auto_excluded || !record.recheck_after) {
311 return '<span class="otto-muted">NA</span>';
312 }
313 var d = new Date(record.recheck_after.replace(' ', 'T'));
314 if (isNaN(d.getTime())) {
315 return escapeHtml(record.recheck_after);
316 }
317 var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
318 return months[d.getMonth()] + ' ' + d.getDate() + ', ' + d.getFullYear();
319 }
320
321 function escapeHtml(text) {
322 if (!text) return '';
323 var map = {
324 '&': '&amp;',
325 '<': '&lt;',
326 '>': '&gt;',
327 '"': '&quot;',
328 "'": '&#039;'
329 };
330 return String(text).replace(/[&<>"']/g, function (m) { return map[m]; });
331 }
332 });
333 })();
334