PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.11
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.11
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 / media-optimization / assets / js / media-optimization-library.js

media-optimization-library.js in Search Atlas SEO – OTTO AI SEO Automation for WordPress 2.6.11, at media-optimization/assets/js/media-optimization-library.js

498 lines 19.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Media Optimization Image Library Scripts
3 *
4 * @package Search Atlas SEO
5 * @copyright Copyright (C) 2021-2025, Search Atlas Group - [email protected]
6 * @since 2.6.0
7 *
8 * Localized data expected via metasyncMediaLib:
9 * - ajaxUrl: string
10 * - nonce: string
11 * - batchRunning: bool
12 * - i18n.optimizing: string
13 * - i18n.optimize: string
14 * - i18n.revert: string
15 * - i18n.revertConfirm: string
16 * - i18n.optimizeFailed:string
17 * - i18n.revertFailed: string
18 * - i18n.startBatch: string
19 * - i18n.batchFailed: string
20 * - i18n.batchComplete: string
21 * - i18n.imagesProcessed: string
22 * - i18n.failed: string
23 * - i18n.optimizingOf: string (e.g. "Optimizing")
24 * - i18n.of: string
25 * - i18n.images: string (e.g. "images...")
26 * - i18n.unoptimized: string
27 * - i18n.selectImages: string
28 * - i18n.bulkFailed: string
29 * - i18n.apply: string
30 * - i18n.unoptimizing: string
31 * - i18n.unoptimizeConfirm: string
32 * - i18n.bulkUnoptimizeFailed: string
33 * - i18n.alreadyUnoptimized: string
34 */
35 (function() {
36 'use strict';
37
38 var config = window.metasyncMediaLib || {};
39 var nonce = config.nonce || '';
40 var ajaxUrl = config.ajaxUrl || window.ajaxurl;
41 var i18n = config.i18n || {};
42 var batchActive = false;
43 var fallbackPoll = null;
44
45 function sanitizeHtml(str) {
46 var div = document.createElement('div');
47 div.textContent = str;
48 return div.innerHTML;
49 }
50
51 function sanitizeTrustedHtml(html) {
52 var parser = new DOMParser();
53 var doc = parser.parseFromString(html, 'text/html');
54 doc.querySelectorAll('script,iframe,object,embed,form,link[rel="import"]').forEach(function(el) { el.remove(); });
55 return doc.body.innerHTML;
56 }
57
58 // ── Single Optimize ──
59 document.addEventListener('click', function(e) {
60 var btn = e.target.closest('.metasync-optimize-btn');
61 if (!btn) return;
62
63 var id = btn.dataset.id;
64 btn.classList.add('loading');
65 btn.disabled = true;
66 btn.innerHTML = '<span class="metasync-batch-spinner" style="width:14px;height:14px;display:inline-block;"></span> ' + sanitizeHtml(i18n.optimizing || 'Optimizing...');
67
68 fetch(ajaxUrl, {
69 method: 'POST',
70 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
71 body: 'action=metasync_optimize_single_image&nonce=' + nonce + '&attachment_id=' + encodeURIComponent(id)
72 })
73 .then(function(r) { return r.json(); })
74 .then(function(data) {
75 if (data.success) {
76 var row = btn.closest('tr');
77 var statusCell = row.querySelector('.column-status');
78 var parser = new DOMParser();
79 var statusDoc = parser.parseFromString(data.data.status_html || '', 'text/html');
80 statusDoc.querySelectorAll('script,iframe,object,embed,form').forEach(function(el) { el.remove(); });
81 while (statusCell.firstChild) { statusCell.removeChild(statusCell.firstChild); }
82 Array.prototype.forEach.call(statusDoc.body.childNodes, function(node) {
83 statusCell.appendChild(node.cloneNode(true));
84 });
85
86 var actionsCell = row.querySelector('.column-actions');
87 if (data.data.can_revert === false) {
88 actionsCell.innerHTML = '<button type="button" class="button button-small metasync-revert-btn" data-id="' + sanitizeHtml(String(id)) + '" disabled title="' + sanitizeHtml(i18n.revertDisabled || 'Original image unavailable — revert is disabled') + '">' +
89 '<span class="dashicons dashicons-undo" style="margin-top:3px;"></span> ' + sanitizeHtml(i18n.revert || 'Revert') + '</button>';
90 } else {
91 actionsCell.innerHTML = '<button type="button" class="button button-small metasync-revert-btn" data-id="' + sanitizeHtml(String(id)) + '">' +
92 '<span class="dashicons dashicons-undo" style="margin-top:3px;"></span> ' + sanitizeHtml(i18n.revert || 'Revert') + '</button>';
93 }
94
95 updateStats();
96 } else {
97 btn.classList.remove('loading');
98 btn.disabled = false;
99 btn.innerHTML = '<span class="dashicons dashicons-performance" style="margin-top:3px;"></span> ' + sanitizeHtml(i18n.optimize || 'Optimize');
100 alert(data.data || (i18n.optimizeFailed || 'Optimization failed.'));
101 }
102 })
103 .catch(function() {
104 btn.classList.remove('loading');
105 btn.disabled = false;
106 btn.innerHTML = '<span class="dashicons dashicons-performance" style="margin-top:3px;"></span> ' + sanitizeHtml(i18n.optimize || 'Optimize');
107 });
108 });
109
110 // ── Single Revert ──
111 document.addEventListener('click', function(e) {
112 var btn = e.target.closest('.metasync-revert-btn');
113 if (!btn || btn.disabled) return;
114
115 if (!confirm(i18n.revertConfirm || 'Revert this image to its original format?')) {
116 return;
117 }
118
119 var id = btn.dataset.id;
120 btn.classList.add('loading');
121 btn.disabled = true;
122
123 fetch(ajaxUrl, {
124 method: 'POST',
125 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
126 body: 'action=metasync_revert_single_image&nonce=' + nonce + '&attachment_id=' + id
127 })
128 .then(function(r) { return r.json(); })
129 .then(function(data) {
130 if (data.success) {
131 var row = btn.closest('tr');
132 var statusCell = row.querySelector('.column-status');
133 statusCell.innerHTML = '<span class="metasync-status-badge metasync-status-unoptimized">' + (i18n.unoptimized || 'Unoptimized') + '</span>';
134
135 var actionsCell = row.querySelector('.column-actions');
136 actionsCell.innerHTML = '<button type="button" class="button button-small button-primary metasync-optimize-btn" data-id="' + id + '">' +
137 '<span class="dashicons dashicons-performance" style="margin-top:3px;"></span> ' + (i18n.optimize || 'Optimize') + '</button>';
138
139 updateStats();
140 } else {
141 btn.classList.remove('loading');
142 btn.disabled = false;
143 alert(data.data || (i18n.revertFailed || 'Revert failed.'));
144 }
145 })
146 .catch(function() {
147 btn.classList.remove('loading');
148 btn.disabled = false;
149 });
150 });
151
152 // ── Optimize All (Start Batch) ──
153 var optimizeAllBtn = document.getElementById('metasync-optimize-all');
154 if (optimizeAllBtn) {
155 optimizeAllBtn.addEventListener('click', function() {
156 if (!confirm(i18n.startBatch || 'Start optimizing all unoptimized images?')) {
157 return;
158 }
159
160 optimizeAllBtn.disabled = true;
161
162 fetch(ajaxUrl, {
163 method: 'POST',
164 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
165 body: 'action=metasync_start_batch_optimize&nonce=' + nonce
166 })
167 .then(function(r) { return r.json(); })
168 .then(function(data) {
169 if (data.success) {
170 showBatchProgress(data.data);
171 startBatchProcessing();
172 } else {
173 optimizeAllBtn.disabled = false;
174 alert(data.data || (i18n.batchFailed || 'Failed to start batch optimization.'));
175 }
176 })
177 .catch(function() {
178 optimizeAllBtn.disabled = false;
179 });
180 });
181 }
182
183 // ── Cancel Batch ──
184 var cancelBtn = document.getElementById('metasync-cancel-batch');
185 if (cancelBtn) {
186 cancelBtn.addEventListener('click', function() {
187 batchActive = false;
188 stopFallbackPoll();
189
190 fetch(ajaxUrl, {
191 method: 'POST',
192 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
193 body: 'action=metasync_cancel_batch_optimize&nonce=' + nonce
194 })
195 .then(function(r) { return r.json(); })
196 .then(function() {
197 document.getElementById('metasync-batch-progress').style.display = 'none';
198 if (optimizeAllBtn) optimizeAllBtn.disabled = false;
199 location.reload();
200 });
201 });
202 }
203
204 // ── Copy URL ──
205 document.addEventListener('click', function(e) {
206 var btn = e.target.closest('.metasync-copy-btn');
207 if (!btn) return;
208
209 var url = btn.dataset.url;
210 if (!url) return;
211
212 navigator.clipboard.writeText(url).then(function() {
213 btn.classList.add('copied');
214 var icon = btn.querySelector('.dashicons');
215 if (icon) {
216 icon.className = 'dashicons dashicons-yes';
217 setTimeout(function() {
218 icon.className = 'dashicons dashicons-clipboard';
219 btn.classList.remove('copied');
220 }, 1500);
221 }
222 });
223 });
224
225 // ── Dismiss Complete Notice ──
226 var dismissBtn = document.getElementById('metasync-dismiss-complete');
227 if (dismissBtn) {
228 dismissBtn.addEventListener('click', function() {
229 document.getElementById('metasync-batch-complete').style.display = 'none';
230 });
231 }
232
233 // ── AJAX-Driven Batch Processing ──
234
235 function startBatchProcessing() {
236 batchActive = true;
237 startFallbackPoll();
238 processNextBatch();
239 }
240
241 function processNextBatch() {
242 if (!batchActive) return;
243
244 fetch(ajaxUrl, {
245 method: 'POST',
246 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
247 body: 'action=metasync_process_batch_tick&nonce=' + nonce
248 })
249 .then(function(r) { return r.json(); })
250 .then(function(data) {
251 if (!data.success || !batchActive) return;
252
253 var progress = data.data;
254 showBatchProgress(progress);
255 applyStats(progress.stats);
256
257 if (progress.status === 'running') {
258 setTimeout(processNextBatch, 500);
259 } else {
260 onBatchFinished(progress);
261 }
262 })
263 .catch(function() {
264 if (batchActive) {
265 setTimeout(processNextBatch, 5000);
266 }
267 });
268 }
269
270 function onBatchFinished(progress) {
271 batchActive = false;
272 stopFallbackPoll();
273 document.getElementById('metasync-batch-progress').style.display = 'none';
274
275 if (progress.status === 'completed') {
276 var completeEl = document.getElementById('metasync-batch-complete');
277 var textEl = document.getElementById('metasync-batch-complete-text');
278 textEl.textContent = (i18n.batchComplete || 'Batch optimization complete!') + ' ' +
279 progress.processed + ' ' + (i18n.imagesProcessed || 'images processed') +
280 (progress.failed > 0 ? ', ' + progress.failed + ' ' + (i18n.failed || 'failed') : '') + '.';
281 completeEl.style.display = 'flex';
282 }
283
284 if (optimizeAllBtn) optimizeAllBtn.disabled = false;
285 setTimeout(function() { location.reload(); }, 1500);
286 }
287
288 function startFallbackPoll() {
289 if (fallbackPoll) return;
290 fallbackPoll = setInterval(function() {
291 fetch(ajaxUrl, {
292 method: 'POST',
293 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
294 body: 'action=metasync_batch_progress&nonce=' + nonce
295 })
296 .then(function(r) { return r.json(); })
297 .then(function(data) {
298 if (!data.success) return;
299 showBatchProgress(data.data);
300 applyStats(data.data.stats);
301 if (data.data.status !== 'running') {
302 onBatchFinished(data.data);
303 }
304 });
305 }, 10000);
306 }
307
308 function stopFallbackPoll() {
309 if (fallbackPoll) {
310 clearInterval(fallbackPoll);
311 fallbackPoll = null;
312 }
313 }
314
315 function showBatchProgress(progress) {
316 var el = document.getElementById('metasync-batch-progress');
317 el.style.display = '';
318
319 var pct = progress.total > 0 ? Math.round((progress.processed / progress.total) * 100) : 0;
320 document.getElementById('metasync-batch-fill').style.width = pct + '%';
321 document.getElementById('metasync-batch-text').textContent =
322 (i18n.optimizingOf || 'Optimizing') + ' ' + progress.processed + ' ' + (i18n.of || 'of') + ' ' + progress.total + ' ' + (i18n.images || 'images...');
323 document.getElementById('metasync-batch-processed').textContent = progress.processed;
324 document.getElementById('metasync-batch-total').textContent = progress.total;
325 }
326
327 function applyStats(stats) {
328 if (!stats) return;
329 var statNumbers = document.querySelectorAll('.metasync-stat-number');
330 if (statNumbers.length >= 3) {
331 statNumbers[0].textContent = stats.total;
332 statNumbers[1].textContent = stats.optimized;
333 statNumbers[2].textContent = stats.unoptimized;
334 }
335 var pctEl = document.querySelector('.metasync-stat-percentage');
336 if (pctEl) pctEl.textContent = stats.percentage + '%';
337 var fillEl = document.querySelector('.metasync-stat-progress-fill');
338 if (fillEl) fillEl.style.width = stats.percentage + '%';
339
340 // Update "Optimize All" button state based on unoptimized count
341 if (optimizeAllBtn && !batchActive) {
342 var unoptimized = parseInt(stats.unoptimized, 10) || 0;
343 optimizeAllBtn.disabled = unoptimized === 0;
344
345 var badge = optimizeAllBtn.querySelector('.metasync-count-badge');
346 if (unoptimized > 0) {
347 if (badge) {
348 badge.textContent = stats.unoptimized;
349 } else {
350 badge = document.createElement('span');
351 badge.className = 'metasync-count-badge';
352 badge.textContent = stats.unoptimized;
353 optimizeAllBtn.appendChild(badge);
354 }
355 } else if (badge) {
356 badge.remove();
357 }
358 }
359 }
360
361 function updateStats() {
362 fetch(ajaxUrl, {
363 method: 'POST',
364 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
365 body: 'action=metasync_batch_progress&nonce=' + nonce
366 })
367 .then(function(r) { return r.json(); })
368 .then(function(data) {
369 if (data.success) applyStats(data.data.stats);
370 });
371 }
372
373 // Auto-resume AJAX chain if batch is already running (e.g. page reload)
374 if (config.batchRunning) {
375 startBatchProcessing();
376 }
377
378 // ── Bulk Optimize Selected ──
379 var bulkForm = document.getElementById('metasync-image-library-form');
380 if (bulkForm) {
381 bulkForm.querySelectorAll('input[type="submit"], button[type="submit"]').forEach(function(btn) {
382 btn.addEventListener('click', function() {
383 bulkForm._lastSubmitter = btn;
384 });
385 });
386
387 bulkForm.addEventListener('submit', function(e) {
388 var submitter = e.submitter || bulkForm._lastSubmitter;
389 var applyBtnTop = bulkForm.querySelector('#doaction');
390 var applyBtnBottom = bulkForm.querySelector('#doaction2');
391
392 var isTopApply = submitter && submitter === applyBtnTop;
393 var isBottomApply = submitter && submitter === applyBtnBottom;
394 if (!isTopApply && !isBottomApply) return;
395
396 var actionName = isTopApply ? 'action' : 'action2';
397 var action = bulkForm.querySelector('[name="' + actionName + '"]');
398 if (!action || (action.value !== 'bulk_optimize' && action.value !== 'bulk_unoptimize')) return;
399
400 e.preventDefault();
401
402 var checked = bulkForm.querySelectorAll('input[name="image_ids[]"]:checked');
403 if (checked.length === 0) {
404 alert(i18n.selectImages || 'Please select at least one image.');
405 return;
406 }
407
408 var ids = [];
409 checked.forEach(function(cb) { ids.push(cb.value); });
410
411 var isBulkUnoptimize = action.value === 'bulk_unoptimize';
412
413 if (isBulkUnoptimize && !confirm(i18n.unoptimizeConfirm || 'Revert selected images to their original format?')) {
414 return;
415 }
416
417 var clickedBtn = isTopApply ? applyBtnTop : applyBtnBottom;
418 clickedBtn.disabled = true;
419
420 var ajaxAction = isBulkUnoptimize ? 'metasync_bulk_unoptimize_selected' : 'metasync_bulk_optimize_selected';
421 clickedBtn.value = isBulkUnoptimize
422 ? (i18n.unoptimizing || 'Unoptimizing...')
423 : (i18n.optimizing || 'Optimizing...');
424
425 fetch(ajaxUrl, {
426 method: 'POST',
427 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
428 body: 'action=' + ajaxAction + '&nonce=' + nonce + '&ids=' + ids.join(',')
429 })
430 .then(function(r) { return r.json(); })
431 .then(function(data) {
432 if (data.success) {
433 var result = data.data;
434 if (isBulkUnoptimize && result.errors && result.errors.length > 0) {
435 alert(result.errors.join('\n'));
436 }
437 if (isBulkUnoptimize && result.success === 0 && result.skipped > 0) {
438 clickedBtn.disabled = false;
439 clickedBtn.value = i18n.apply || 'Apply';
440 alert(i18n.alreadyUnoptimized || 'All selected images are already unoptimized.');
441 return;
442 }
443 location.reload();
444 } else {
445 clickedBtn.disabled = false;
446 clickedBtn.value = i18n.apply || 'Apply';
447 var failMsg = isBulkUnoptimize
448 ? (i18n.bulkUnoptimizeFailed || 'Bulk unoptimize failed.')
449 : (i18n.bulkFailed || 'Bulk optimization failed.');
450 alert(data.data || failMsg);
451 }
452 })
453 .catch(function() {
454 clickedBtn.disabled = false;
455 clickedBtn.value = i18n.apply || 'Apply';
456 });
457 });
458 }
459
460 // ── Search Clear Button ──
461 function initSearchClearBtn() {
462 var searchInput = document.querySelector('.metasync-toolbar-right .search-box input[type="search"]');
463 if (!searchInput) return;
464
465 var wrapper = document.createElement('div');
466 wrapper.className = 'metasync-search-input-wrap';
467 searchInput.parentNode.insertBefore(wrapper, searchInput);
468 wrapper.appendChild(searchInput);
469
470 var clearBtn = document.createElement('button');
471 clearBtn.type = 'button';
472 clearBtn.className = 'metasync-clear-search';
473 clearBtn.innerHTML = '&times;';
474 clearBtn.setAttribute('aria-label', 'Clear search');
475 wrapper.appendChild(clearBtn);
476
477 function updateVisibility() {
478 if (searchInput.value.length > 0) {
479 clearBtn.classList.add('visible');
480 } else {
481 clearBtn.classList.remove('visible');
482 }
483 }
484
485 searchInput.addEventListener('input', updateVisibility);
486 updateVisibility();
487
488 clearBtn.addEventListener('click', function() {
489 searchInput.value = '';
490 updateVisibility();
491 var form = searchInput.closest('form');
492 if (form) { form.submit(); }
493 });
494 }
495
496 initSearchClearBtn();
497 })();
498