PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.19
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.19
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 / views / js / metasync-redirections.js

metasync-redirections.js in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.19, at views/js/metasync-redirections.js

209 lines 7.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * MetaSync redirections — filter auto-submit and pagination tab fix.
3 *
4 * Extracted from views/metasync-redirection.php (Phase 5, #887).
5 */
6 document.addEventListener('DOMContentLoaded', function () {
7 // Auto-submit filters when changed
8 var filterSelects = document.querySelectorAll('#status-filter, #pattern-filter, #http-code-filter');
9 var form = document.getElementById('redirection-form');
10 filterSelects.forEach(function (select) {
11 select.addEventListener('change', function () {
12 var formAction = form.getAttribute('action') || window.location.href;
13 var url = new URL(formAction, window.location.origin);
14 url.searchParams.delete('paged_redir');
15 form.setAttribute('action', url.pathname + url.search);
16
17 // Also add hidden field to ensure pagination resets
18 var pagedInput = form.querySelector('input[name="paged_redir"]');
19 if (!pagedInput) {
20 pagedInput = document.createElement('input');
21 pagedInput.type = 'hidden';
22 pagedInput.name = 'paged_redir';
23 form.appendChild(pagedInput);
24 }
25 pagedInput.value = '1';
26
27 form.submit();
28 });
29 });
30
31 // Submit the search when Enter is pressed in the search field.
32 //
33 // #redirection-form also contains the (hidden until action=add/edit) "Save"
34 // button from the add/edit-redirection panel, and that button is first in
35 // tree order. Per the HTML spec, pressing Enter in a text field submits the
36 // form via its *default* button (the first submit button in tree order),
37 // even if that button is hidden — so Enter here would otherwise submit as
38 // the "Save" button instead of "Search", which fails the add/edit form's
39 // validation and gets silently cancelled. Explicitly clicking the actual
40 // Search button reproduces exactly what a mouse click does, sidestepping
41 // the browser's implicit-submission button choice.
42 var searchInput = document.getElementById('post-search-input');
43 var searchSubmit = document.getElementById('search-submit');
44 if (searchInput && searchSubmit) {
45 searchInput.addEventListener('keydown', function (e) {
46 if (e.key === 'Enter') {
47 e.preventDefault();
48 searchSubmit.click();
49 }
50 });
51 }
52
53 // Add tab parameter to all pagination links in redirections tab
54 function addTabToPaginationLinks() {
55 // Find all pagination links within redirections-content
56 var redirectionsContent = document.getElementById('redirections-content');
57 if (redirectionsContent) {
58 var paginationLinks = redirectionsContent.querySelectorAll('.tablenav-pages a');
59 paginationLinks.forEach(function (link) {
60 var url = new URL(link.href);
61 url.searchParams.set('tab', 'redirections');
62 link.href = url.toString();
63 });
64 }
65 }
66
67 // Run immediately and after a short delay
68 addTabToPaginationLinks();
69 setTimeout(addTabToPaginationLinks, 100);
70 setTimeout(addTabToPaginationLinks, 500);
71
72 // --- Health Check ---
73 var healthBtn = document.getElementById('metasync-check-health-btn');
74 if (!healthBtn || typeof metasyncHealthCheck === 'undefined') {
75 return;
76 }
77
78 var healthBtnLabel = healthBtn.textContent;
79
80 // Single reusable tooltip appended to body — escapes all stacking contexts
81 var floatingTip = document.createElement('div');
82 floatingTip.className = 'health-tooltip-floating';
83 floatingTip.style.display = 'none';
84 document.body.appendChild(floatingTip);
85
86 // Hide tooltip on scroll to prevent detachment from icon
87 window.addEventListener('scroll', function () {
88 floatingTip.style.display = 'none';
89 }, true);
90
91 // Delegated hover handlers — work for any health cells added now or later
92 document.addEventListener('mouseover', function (e) {
93 var cell = e.target.closest('.health-status-cell[data-health-tip]');
94 if (!cell) {
95 return;
96 }
97 var rect = cell.getBoundingClientRect();
98 floatingTip.textContent = cell.getAttribute('data-health-tip');
99 floatingTip.style.display = 'block';
100 var tipRect = floatingTip.getBoundingClientRect();
101 floatingTip.style.left = (rect.left + rect.width / 2 - tipRect.width / 2) + 'px';
102 floatingTip.style.top = (rect.top - tipRect.height - 6) + 'px';
103 });
104
105 document.addEventListener('mouseout', function (e) {
106 var cell = e.target.closest('.health-status-cell[data-health-tip]');
107 if (!cell) {
108 return;
109 }
110 floatingTip.style.display = 'none';
111 });
112
113 healthBtn.addEventListener('click', function () {
114 healthBtn.disabled = true;
115 healthBtn.innerHTML = healthBtnLabel + ' <span class="spinner is-active" style="float:none;margin:0 0 0 6px;vertical-align:middle;"></span>';
116
117 var data = new FormData();
118 data.append('action', 'metasync_check_redirects_health');
119 data.append('nonce', metasyncHealthCheck.healthNonce);
120
121 fetch(metasyncHealthCheck.ajaxUrl, { method: 'POST', body: data, credentials: 'same-origin' })
122 .then(function (r) {
123 return r.json();
124 })
125 .then(function (resp) {
126 healthBtn.disabled = false;
127 healthBtn.textContent = healthBtnLabel;
128
129 if (!resp.success || !resp.data || !resp.data.results) {
130 return;
131 }
132
133 var results = resp.data.results;
134 var map = {};
135 results.forEach(function (r) {
136 map[r.id] = r;
137 });
138
139 var statusIcons = { ok: '�
140 ', loop: '🔴', chain_too_long: '⚠️', dead_end: '💀' };
141 var statusLabels = { ok: 'OK', loop: 'Loop detected', chain_too_long: 'Chain too long', dead_end: 'Dead end' };
142
143 // Insert header column
144 var table = document.querySelector('.wp-list-table');
145 if (!table) {
146 return;
147 }
148
149 // Remove existing health columns (re-run support)
150 table.querySelectorAll('.column-health_status').forEach(function (el) {
151 el.remove();
152 });
153
154 var headerRows = table.querySelectorAll('thead tr, tfoot tr');
155 headerRows.forEach(function (tr) {
156 var statusTh = tr.querySelector('.column-status');
157 if (!statusTh) {
158 // Fallback: insert at end
159 statusTh = tr.lastElementChild;
160 }
161 var th = document.createElement('th');
162 th.className = 'manage-column column-health_status';
163 th.textContent = 'Health';
164 statusTh.parentNode.insertBefore(th, statusTh.nextSibling);
165 });
166
167 // Insert cell per row
168 var bodyRows = table.querySelectorAll('tbody tr');
169 bodyRows.forEach(function (tr) {
170 var cb = tr.querySelector('input[type="checkbox"]');
171 var id = cb ? parseInt(cb.value, 10) : 0;
172 var info = map[id];
173
174 var td = document.createElement('td');
175 td.className = 'column-health_status';
176
177 if (info) {
178 var icon = statusIcons[info.status] || '❓';
179 var label = statusLabels[info.status] || info.status;
180 var tipText = label;
181 if (info.status !== 'ok' && info.chain && info.chain.length > 0) {
182 tipText += ': ' + info.chain.join(' \u2192 ');
183 }
184 td.innerHTML = '<span class="health-status-cell" data-health-tip="' +
185 tipText.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;') +
186 '">' + icon + '</span>';
187 } else {
188 td.textContent = '\u2014';
189 }
190
191 var statusTd = tr.querySelector('.column-status');
192 if (statusTd) {
193 statusTd.parentNode.insertBefore(td, statusTd.nextSibling);
194 } else {
195 tr.appendChild(td);
196 }
197 });
198
199
200 // Scroll to the table so results are visible
201 table.scrollIntoView({ behavior: 'smooth', block: 'start' });
202 })
203 .catch(function () {
204 healthBtn.disabled = false;
205 healthBtn.textContent = healthBtnLabel;
206 });
207 });
208 });
209