PluginProbe
Search Atlas SEO – OTTO AI SEO Automation for WordPress / 2.6.23
Search Atlas SEO – OTTO AI SEO Automation for WordPress v2.6.23
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 2.6.4 2.6.3 All 139 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.23, at media-optimization/assets/js/media-optimization-library.js

538 lines 21.3 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 // ── Delete Orphaned Record (missing file) ──
153 document.addEventListener('click', function(e) {
154 var btn = e.target.closest('.metasync-delete-orphan-btn');
155 if (!btn || btn.disabled) return;
156
157 if (!confirm(i18n.deleteOrphanConfirm || 'The file for this image is missing on disk. Delete this orphaned media record? This cannot be undone.')) {
158 return;
159 }
160
161 var id = btn.dataset.id;
162 btn.classList.add('loading');
163 btn.disabled = true;
164
165 fetch(ajaxUrl, {
166 method: 'POST',
167 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
168 body: 'action=metasync_delete_orphaned_image&nonce=' + nonce + '&attachment_id=' + encodeURIComponent(id)
169 })
170 .then(function(r) { return r.json(); })
171 .then(function(data) {
172 if (data.success) {
173 var row = btn.closest('tr');
174 if (row) { row.parentNode.removeChild(row); }
175 if (data.data && data.data.stats) {
176 applyStats(data.data.stats);
177 } else {
178 updateStats();
179 }
180 } else {
181 btn.classList.remove('loading');
182 btn.disabled = false;
183 alert(data.data || (i18n.deleteOrphanFailed || 'Failed to delete the orphaned media record.'));
184 }
185 })
186 .catch(function() {
187 btn.classList.remove('loading');
188 btn.disabled = false;
189 });
190 });
191
192 // ── Optimize All (Start Batch) ──
193 var optimizeAllBtn = document.getElementById('metasync-optimize-all');
194 if (optimizeAllBtn) {
195 optimizeAllBtn.addEventListener('click', function() {
196 if (!confirm(i18n.startBatch || 'Start optimizing all unoptimized images?')) {
197 return;
198 }
199
200 optimizeAllBtn.disabled = true;
201
202 fetch(ajaxUrl, {
203 method: 'POST',
204 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
205 body: 'action=metasync_start_batch_optimize&nonce=' + nonce
206 })
207 .then(function(r) { return r.json(); })
208 .then(function(data) {
209 if (data.success) {
210 showBatchProgress(data.data);
211 startBatchProcessing();
212 } else {
213 optimizeAllBtn.disabled = false;
214 alert(data.data || (i18n.batchFailed || 'Failed to start batch optimization.'));
215 }
216 })
217 .catch(function() {
218 optimizeAllBtn.disabled = false;
219 });
220 });
221 }
222
223 // ── Cancel Batch ──
224 var cancelBtn = document.getElementById('metasync-cancel-batch');
225 if (cancelBtn) {
226 cancelBtn.addEventListener('click', function() {
227 batchActive = false;
228 stopFallbackPoll();
229
230 fetch(ajaxUrl, {
231 method: 'POST',
232 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
233 body: 'action=metasync_cancel_batch_optimize&nonce=' + nonce
234 })
235 .then(function(r) { return r.json(); })
236 .then(function() {
237 document.getElementById('metasync-batch-progress').style.display = 'none';
238 if (optimizeAllBtn) optimizeAllBtn.disabled = false;
239 location.reload();
240 });
241 });
242 }
243
244 // ── Copy URL ──
245 document.addEventListener('click', function(e) {
246 var btn = e.target.closest('.metasync-copy-btn');
247 if (!btn) return;
248
249 var url = btn.dataset.url;
250 if (!url) return;
251
252 navigator.clipboard.writeText(url).then(function() {
253 btn.classList.add('copied');
254 var icon = btn.querySelector('.dashicons');
255 if (icon) {
256 icon.className = 'dashicons dashicons-yes';
257 setTimeout(function() {
258 icon.className = 'dashicons dashicons-clipboard';
259 btn.classList.remove('copied');
260 }, 1500);
261 }
262 });
263 });
264
265 // ── Dismiss Complete Notice ──
266 var dismissBtn = document.getElementById('metasync-dismiss-complete');
267 if (dismissBtn) {
268 dismissBtn.addEventListener('click', function() {
269 document.getElementById('metasync-batch-complete').style.display = 'none';
270 });
271 }
272
273 // ── AJAX-Driven Batch Processing ──
274
275 function startBatchProcessing() {
276 batchActive = true;
277 startFallbackPoll();
278 processNextBatch();
279 }
280
281 function processNextBatch() {
282 if (!batchActive) return;
283
284 fetch(ajaxUrl, {
285 method: 'POST',
286 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
287 body: 'action=metasync_process_batch_tick&nonce=' + nonce
288 })
289 .then(function(r) { return r.json(); })
290 .then(function(data) {
291 if (!data.success || !batchActive) return;
292
293 var progress = data.data;
294 showBatchProgress(progress);
295 applyStats(progress.stats);
296
297 if (progress.status === 'running') {
298 setTimeout(processNextBatch, 500);
299 } else {
300 onBatchFinished(progress);
301 }
302 })
303 .catch(function() {
304 if (batchActive) {
305 setTimeout(processNextBatch, 5000);
306 }
307 });
308 }
309
310 function onBatchFinished(progress) {
311 batchActive = false;
312 stopFallbackPoll();
313 document.getElementById('metasync-batch-progress').style.display = 'none';
314
315 if (progress.status === 'completed') {
316 var completeEl = document.getElementById('metasync-batch-complete');
317 var textEl = document.getElementById('metasync-batch-complete-text');
318 textEl.textContent = (i18n.batchComplete || 'Batch optimization complete!') + ' ' +
319 progress.processed + ' ' + (i18n.imagesProcessed || 'images processed') +
320 (progress.failed > 0 ? ', ' + progress.failed + ' ' + (i18n.failed || 'failed') : '') + '.';
321 completeEl.style.display = 'flex';
322 }
323
324 if (optimizeAllBtn) optimizeAllBtn.disabled = false;
325 setTimeout(function() { location.reload(); }, 1500);
326 }
327
328 function startFallbackPoll() {
329 if (fallbackPoll) return;
330 fallbackPoll = setInterval(function() {
331 fetch(ajaxUrl, {
332 method: 'POST',
333 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
334 body: 'action=metasync_batch_progress&nonce=' + nonce
335 })
336 .then(function(r) { return r.json(); })
337 .then(function(data) {
338 if (!data.success) return;
339 showBatchProgress(data.data);
340 applyStats(data.data.stats);
341 if (data.data.status !== 'running') {
342 onBatchFinished(data.data);
343 }
344 });
345 }, 10000);
346 }
347
348 function stopFallbackPoll() {
349 if (fallbackPoll) {
350 clearInterval(fallbackPoll);
351 fallbackPoll = null;
352 }
353 }
354
355 function showBatchProgress(progress) {
356 var el = document.getElementById('metasync-batch-progress');
357 el.style.display = '';
358
359 var pct = progress.total > 0 ? Math.round((progress.processed / progress.total) * 100) : 0;
360 document.getElementById('metasync-batch-fill').style.width = pct + '%';
361 document.getElementById('metasync-batch-text').textContent =
362 (i18n.optimizingOf || 'Optimizing') + ' ' + progress.processed + ' ' + (i18n.of || 'of') + ' ' + progress.total + ' ' + (i18n.images || 'images...');
363 document.getElementById('metasync-batch-processed').textContent = progress.processed;
364 document.getElementById('metasync-batch-total').textContent = progress.total;
365 }
366
367 function applyStats(stats) {
368 if (!stats) return;
369 var statNumbers = document.querySelectorAll('.metasync-stat-number');
370 if (statNumbers.length >= 3) {
371 statNumbers[0].textContent = stats.total;
372 statNumbers[1].textContent = stats.optimized;
373 statNumbers[2].textContent = stats.unoptimized;
374 }
375 var pctEl = document.querySelector('.metasync-stat-percentage');
376 if (pctEl) pctEl.textContent = stats.percentage + '%';
377 var fillEl = document.querySelector('.metasync-stat-progress-fill');
378 if (fillEl) fillEl.style.width = stats.percentage + '%';
379
380 // Update "Optimize All" button state based on unoptimized count
381 if (optimizeAllBtn && !batchActive) {
382 var unoptimized = parseInt(stats.unoptimized, 10) || 0;
383 optimizeAllBtn.disabled = unoptimized === 0;
384
385 var badge = optimizeAllBtn.querySelector('.metasync-count-badge');
386 if (unoptimized > 0) {
387 if (badge) {
388 badge.textContent = stats.unoptimized;
389 } else {
390 badge = document.createElement('span');
391 badge.className = 'metasync-count-badge';
392 badge.textContent = stats.unoptimized;
393 optimizeAllBtn.appendChild(badge);
394 }
395 } else if (badge) {
396 badge.remove();
397 }
398 }
399 }
400
401 function updateStats() {
402 fetch(ajaxUrl, {
403 method: 'POST',
404 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
405 body: 'action=metasync_batch_progress&nonce=' + nonce
406 })
407 .then(function(r) { return r.json(); })
408 .then(function(data) {
409 if (data.success) applyStats(data.data.stats);
410 });
411 }
412
413 // Auto-resume AJAX chain if batch is already running (e.g. page reload)
414 if (config.batchRunning) {
415 startBatchProcessing();
416 }
417
418 // ── Bulk Optimize Selected ──
419 var bulkForm = document.getElementById('metasync-image-library-form');
420 if (bulkForm) {
421 bulkForm.querySelectorAll('input[type="submit"], button[type="submit"]').forEach(function(btn) {
422 btn.addEventListener('click', function() {
423 bulkForm._lastSubmitter = btn;
424 });
425 });
426
427 bulkForm.addEventListener('submit', function(e) {
428 var submitter = e.submitter || bulkForm._lastSubmitter;
429 var applyBtnTop = bulkForm.querySelector('#doaction');
430 var applyBtnBottom = bulkForm.querySelector('#doaction2');
431
432 var isTopApply = submitter && submitter === applyBtnTop;
433 var isBottomApply = submitter && submitter === applyBtnBottom;
434 if (!isTopApply && !isBottomApply) return;
435
436 var actionName = isTopApply ? 'action' : 'action2';
437 var action = bulkForm.querySelector('[name="' + actionName + '"]');
438 if (!action || (action.value !== 'bulk_optimize' && action.value !== 'bulk_unoptimize')) return;
439
440 e.preventDefault();
441
442 var checked = bulkForm.querySelectorAll('input[name="image_ids[]"]:checked');
443 if (checked.length === 0) {
444 alert(i18n.selectImages || 'Please select at least one image.');
445 return;
446 }
447
448 var ids = [];
449 checked.forEach(function(cb) { ids.push(cb.value); });
450
451 var isBulkUnoptimize = action.value === 'bulk_unoptimize';
452
453 if (isBulkUnoptimize && !confirm(i18n.unoptimizeConfirm || 'Revert selected images to their original format?')) {
454 return;
455 }
456
457 var clickedBtn = isTopApply ? applyBtnTop : applyBtnBottom;
458 clickedBtn.disabled = true;
459
460 var ajaxAction = isBulkUnoptimize ? 'metasync_bulk_unoptimize_selected' : 'metasync_bulk_optimize_selected';
461 clickedBtn.value = isBulkUnoptimize
462 ? (i18n.unoptimizing || 'Unoptimizing...')
463 : (i18n.optimizing || 'Optimizing...');
464
465 fetch(ajaxUrl, {
466 method: 'POST',
467 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
468 body: 'action=' + ajaxAction + '&nonce=' + nonce + '&ids=' + ids.join(',')
469 })
470 .then(function(r) { return r.json(); })
471 .then(function(data) {
472 if (data.success) {
473 var result = data.data;
474 if (isBulkUnoptimize && result.errors && result.errors.length > 0) {
475 alert(result.errors.join('\n'));
476 }
477 if (isBulkUnoptimize && result.success === 0 && result.skipped > 0) {
478 clickedBtn.disabled = false;
479 clickedBtn.value = i18n.apply || 'Apply';
480 alert(i18n.alreadyUnoptimized || 'All selected images are already unoptimized.');
481 return;
482 }
483 location.reload();
484 } else {
485 clickedBtn.disabled = false;
486 clickedBtn.value = i18n.apply || 'Apply';
487 var failMsg = isBulkUnoptimize
488 ? (i18n.bulkUnoptimizeFailed || 'Bulk unoptimize failed.')
489 : (i18n.bulkFailed || 'Bulk optimization failed.');
490 alert(data.data || failMsg);
491 }
492 })
493 .catch(function() {
494 clickedBtn.disabled = false;
495 clickedBtn.value = i18n.apply || 'Apply';
496 });
497 });
498 }
499
500 // ── Search Clear Button ──
501 function initSearchClearBtn() {
502 var searchInput = document.querySelector('.metasync-toolbar-right .search-box input[type="search"]');
503 if (!searchInput) return;
504
505 var wrapper = document.createElement('div');
506 wrapper.className = 'metasync-search-input-wrap';
507 searchInput.parentNode.insertBefore(wrapper, searchInput);
508 wrapper.appendChild(searchInput);
509
510 var clearBtn = document.createElement('button');
511 clearBtn.type = 'button';
512 clearBtn.className = 'metasync-clear-search';
513 clearBtn.innerHTML = '&times;';
514 clearBtn.setAttribute('aria-label', 'Clear search');
515 wrapper.appendChild(clearBtn);
516
517 function updateVisibility() {
518 if (searchInput.value.length > 0) {
519 clearBtn.classList.add('visible');
520 } else {
521 clearBtn.classList.remove('visible');
522 }
523 }
524
525 searchInput.addEventListener('input', updateVisibility);
526 updateVisibility();
527
528 clearBtn.addEventListener('click', function() {
529 searchInput.value = '';
530 updateVisibility();
531 var form = searchInput.closest('form');
532 if (form) { form.submit(); }
533 });
534 }
535
536 initSearchClearBtn();
537 })();
538