/** * Media Optimization Image Library Scripts * * @package Search Atlas SEO * @copyright Copyright (C) 2021-2025, Search Atlas Group - support@searchatlas.com * @since 2.6.0 * * Localized data expected via metasyncMediaLib: * - ajaxUrl: string * - nonce: string * - batchRunning: bool * - i18n.optimizing: string * - i18n.optimize: string * - i18n.revert: string * - i18n.revertConfirm: string * - i18n.optimizeFailed:string * - i18n.revertFailed: string * - i18n.startBatch: string * - i18n.batchFailed: string * - i18n.batchComplete: string * - i18n.imagesProcessed: string * - i18n.failed: string * - i18n.optimizingOf: string (e.g. "Optimizing") * - i18n.of: string * - i18n.images: string (e.g. "images...") * - i18n.unoptimized: string * - i18n.selectImages: string * - i18n.bulkFailed: string * - i18n.apply: string * - i18n.unoptimizing: string * - i18n.unoptimizeConfirm: string * - i18n.bulkUnoptimizeFailed: string * - i18n.alreadyUnoptimized: string */ (function() { 'use strict'; var config = window.metasyncMediaLib || {}; var nonce = config.nonce || ''; var ajaxUrl = config.ajaxUrl || window.ajaxurl; var i18n = config.i18n || {}; var batchActive = false; var fallbackPoll = null; function sanitizeHtml(str) { var div = document.createElement('div'); div.textContent = str; return div.innerHTML; } function sanitizeTrustedHtml(html) { var parser = new DOMParser(); var doc = parser.parseFromString(html, 'text/html'); doc.querySelectorAll('script,iframe,object,embed,form,link[rel="import"]').forEach(function(el) { el.remove(); }); return doc.body.innerHTML; } // ── Single Optimize ── document.addEventListener('click', function(e) { var btn = e.target.closest('.metasync-optimize-btn'); if (!btn) return; var id = btn.dataset.id; btn.classList.add('loading'); btn.disabled = true; btn.innerHTML = ' ' + sanitizeHtml(i18n.optimizing || 'Optimizing...'); fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'action=metasync_optimize_single_image&nonce=' + nonce + '&attachment_id=' + encodeURIComponent(id) }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { var row = btn.closest('tr'); var statusCell = row.querySelector('.column-status'); var parser = new DOMParser(); var statusDoc = parser.parseFromString(data.data.status_html || '', 'text/html'); statusDoc.querySelectorAll('script,iframe,object,embed,form').forEach(function(el) { el.remove(); }); while (statusCell.firstChild) { statusCell.removeChild(statusCell.firstChild); } Array.prototype.forEach.call(statusDoc.body.childNodes, function(node) { statusCell.appendChild(node.cloneNode(true)); }); var actionsCell = row.querySelector('.column-actions'); if (data.data.can_revert === false) { actionsCell.innerHTML = ''; } else { actionsCell.innerHTML = ''; } updateStats(); } else { btn.classList.remove('loading'); btn.disabled = false; btn.innerHTML = ' ' + sanitizeHtml(i18n.optimize || 'Optimize'); alert(data.data || (i18n.optimizeFailed || 'Optimization failed.')); } }) .catch(function() { btn.classList.remove('loading'); btn.disabled = false; btn.innerHTML = ' ' + sanitizeHtml(i18n.optimize || 'Optimize'); }); }); // ── Single Revert ── document.addEventListener('click', function(e) { var btn = e.target.closest('.metasync-revert-btn'); if (!btn || btn.disabled) return; if (!confirm(i18n.revertConfirm || 'Revert this image to its original format?')) { return; } var id = btn.dataset.id; btn.classList.add('loading'); btn.disabled = true; fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'action=metasync_revert_single_image&nonce=' + nonce + '&attachment_id=' + id }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { var row = btn.closest('tr'); var statusCell = row.querySelector('.column-status'); statusCell.innerHTML = '' + (i18n.unoptimized || 'Unoptimized') + ''; var actionsCell = row.querySelector('.column-actions'); actionsCell.innerHTML = ''; updateStats(); } else { btn.classList.remove('loading'); btn.disabled = false; alert(data.data || (i18n.revertFailed || 'Revert failed.')); } }) .catch(function() { btn.classList.remove('loading'); btn.disabled = false; }); }); // ── Delete Orphaned Record (missing file) ── document.addEventListener('click', function(e) { var btn = e.target.closest('.metasync-delete-orphan-btn'); if (!btn || btn.disabled) return; if (!confirm(i18n.deleteOrphanConfirm || 'The file for this image is missing on disk. Delete this orphaned media record? This cannot be undone.')) { return; } var id = btn.dataset.id; btn.classList.add('loading'); btn.disabled = true; fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'action=metasync_delete_orphaned_image&nonce=' + nonce + '&attachment_id=' + encodeURIComponent(id) }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { var row = btn.closest('tr'); if (row) { row.parentNode.removeChild(row); } if (data.data && data.data.stats) { applyStats(data.data.stats); } else { updateStats(); } } else { btn.classList.remove('loading'); btn.disabled = false; alert(data.data || (i18n.deleteOrphanFailed || 'Failed to delete the orphaned media record.')); } }) .catch(function() { btn.classList.remove('loading'); btn.disabled = false; }); }); // ── Optimize All (Start Batch) ── var optimizeAllBtn = document.getElementById('metasync-optimize-all'); if (optimizeAllBtn) { optimizeAllBtn.addEventListener('click', function() { if (!confirm(i18n.startBatch || 'Start optimizing all unoptimized images?')) { return; } optimizeAllBtn.disabled = true; fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'action=metasync_start_batch_optimize&nonce=' + nonce }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { showBatchProgress(data.data); startBatchProcessing(); } else { optimizeAllBtn.disabled = false; alert(data.data || (i18n.batchFailed || 'Failed to start batch optimization.')); } }) .catch(function() { optimizeAllBtn.disabled = false; }); }); } // ── Cancel Batch ── var cancelBtn = document.getElementById('metasync-cancel-batch'); if (cancelBtn) { cancelBtn.addEventListener('click', function() { batchActive = false; stopFallbackPoll(); fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'action=metasync_cancel_batch_optimize&nonce=' + nonce }) .then(function(r) { return r.json(); }) .then(function() { document.getElementById('metasync-batch-progress').style.display = 'none'; if (optimizeAllBtn) optimizeAllBtn.disabled = false; location.reload(); }); }); } // ── Copy URL ── document.addEventListener('click', function(e) { var btn = e.target.closest('.metasync-copy-btn'); if (!btn) return; var url = btn.dataset.url; if (!url) return; navigator.clipboard.writeText(url).then(function() { btn.classList.add('copied'); var icon = btn.querySelector('.dashicons'); if (icon) { icon.className = 'dashicons dashicons-yes'; setTimeout(function() { icon.className = 'dashicons dashicons-clipboard'; btn.classList.remove('copied'); }, 1500); } }); }); // ── Dismiss Complete Notice ── var dismissBtn = document.getElementById('metasync-dismiss-complete'); if (dismissBtn) { dismissBtn.addEventListener('click', function() { document.getElementById('metasync-batch-complete').style.display = 'none'; }); } // ── AJAX-Driven Batch Processing ── function startBatchProcessing() { batchActive = true; startFallbackPoll(); processNextBatch(); } function processNextBatch() { if (!batchActive) return; fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'action=metasync_process_batch_tick&nonce=' + nonce }) .then(function(r) { return r.json(); }) .then(function(data) { if (!data.success || !batchActive) return; var progress = data.data; showBatchProgress(progress); applyStats(progress.stats); if (progress.status === 'running') { setTimeout(processNextBatch, 500); } else { onBatchFinished(progress); } }) .catch(function() { if (batchActive) { setTimeout(processNextBatch, 5000); } }); } function onBatchFinished(progress) { batchActive = false; stopFallbackPoll(); document.getElementById('metasync-batch-progress').style.display = 'none'; if (progress.status === 'completed') { var completeEl = document.getElementById('metasync-batch-complete'); var textEl = document.getElementById('metasync-batch-complete-text'); textEl.textContent = (i18n.batchComplete || 'Batch optimization complete!') + ' ' + progress.processed + ' ' + (i18n.imagesProcessed || 'images processed') + (progress.failed > 0 ? ', ' + progress.failed + ' ' + (i18n.failed || 'failed') : '') + '.'; completeEl.style.display = 'flex'; } if (optimizeAllBtn) optimizeAllBtn.disabled = false; setTimeout(function() { location.reload(); }, 1500); } function startFallbackPoll() { if (fallbackPoll) return; fallbackPoll = setInterval(function() { fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'action=metasync_batch_progress&nonce=' + nonce }) .then(function(r) { return r.json(); }) .then(function(data) { if (!data.success) return; showBatchProgress(data.data); applyStats(data.data.stats); if (data.data.status !== 'running') { onBatchFinished(data.data); } }); }, 10000); } function stopFallbackPoll() { if (fallbackPoll) { clearInterval(fallbackPoll); fallbackPoll = null; } } function showBatchProgress(progress) { var el = document.getElementById('metasync-batch-progress'); el.style.display = ''; var pct = progress.total > 0 ? Math.round((progress.processed / progress.total) * 100) : 0; document.getElementById('metasync-batch-fill').style.width = pct + '%'; document.getElementById('metasync-batch-text').textContent = (i18n.optimizingOf || 'Optimizing') + ' ' + progress.processed + ' ' + (i18n.of || 'of') + ' ' + progress.total + ' ' + (i18n.images || 'images...'); document.getElementById('metasync-batch-processed').textContent = progress.processed; document.getElementById('metasync-batch-total').textContent = progress.total; } function applyStats(stats) { if (!stats) return; var statNumbers = document.querySelectorAll('.metasync-stat-number'); if (statNumbers.length >= 3) { statNumbers[0].textContent = stats.total; statNumbers[1].textContent = stats.optimized; statNumbers[2].textContent = stats.unoptimized; } var pctEl = document.querySelector('.metasync-stat-percentage'); if (pctEl) pctEl.textContent = stats.percentage + '%'; var fillEl = document.querySelector('.metasync-stat-progress-fill'); if (fillEl) fillEl.style.width = stats.percentage + '%'; // Update "Optimize All" button state based on unoptimized count if (optimizeAllBtn && !batchActive) { var unoptimized = parseInt(stats.unoptimized, 10) || 0; optimizeAllBtn.disabled = unoptimized === 0; var badge = optimizeAllBtn.querySelector('.metasync-count-badge'); if (unoptimized > 0) { if (badge) { badge.textContent = stats.unoptimized; } else { badge = document.createElement('span'); badge.className = 'metasync-count-badge'; badge.textContent = stats.unoptimized; optimizeAllBtn.appendChild(badge); } } else if (badge) { badge.remove(); } } } function updateStats() { fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'action=metasync_batch_progress&nonce=' + nonce }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) applyStats(data.data.stats); }); } // Auto-resume AJAX chain if batch is already running (e.g. page reload) if (config.batchRunning) { startBatchProcessing(); } // ── Bulk Optimize Selected ── var bulkForm = document.getElementById('metasync-image-library-form'); if (bulkForm) { bulkForm.querySelectorAll('input[type="submit"], button[type="submit"]').forEach(function(btn) { btn.addEventListener('click', function() { bulkForm._lastSubmitter = btn; }); }); bulkForm.addEventListener('submit', function(e) { var submitter = e.submitter || bulkForm._lastSubmitter; var applyBtnTop = bulkForm.querySelector('#doaction'); var applyBtnBottom = bulkForm.querySelector('#doaction2'); var isTopApply = submitter && submitter === applyBtnTop; var isBottomApply = submitter && submitter === applyBtnBottom; if (!isTopApply && !isBottomApply) return; var actionName = isTopApply ? 'action' : 'action2'; var action = bulkForm.querySelector('[name="' + actionName + '"]'); if (!action || (action.value !== 'bulk_optimize' && action.value !== 'bulk_unoptimize')) return; e.preventDefault(); var checked = bulkForm.querySelectorAll('input[name="image_ids[]"]:checked'); if (checked.length === 0) { alert(i18n.selectImages || 'Please select at least one image.'); return; } var ids = []; checked.forEach(function(cb) { ids.push(cb.value); }); var isBulkUnoptimize = action.value === 'bulk_unoptimize'; if (isBulkUnoptimize && !confirm(i18n.unoptimizeConfirm || 'Revert selected images to their original format?')) { return; } var clickedBtn = isTopApply ? applyBtnTop : applyBtnBottom; clickedBtn.disabled = true; var ajaxAction = isBulkUnoptimize ? 'metasync_bulk_unoptimize_selected' : 'metasync_bulk_optimize_selected'; clickedBtn.value = isBulkUnoptimize ? (i18n.unoptimizing || 'Unoptimizing...') : (i18n.optimizing || 'Optimizing...'); fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'action=' + ajaxAction + '&nonce=' + nonce + '&ids=' + ids.join(',') }) .then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { var result = data.data; if (isBulkUnoptimize && result.errors && result.errors.length > 0) { alert(result.errors.join('\n')); } if (isBulkUnoptimize && result.success === 0 && result.skipped > 0) { clickedBtn.disabled = false; clickedBtn.value = i18n.apply || 'Apply'; alert(i18n.alreadyUnoptimized || 'All selected images are already unoptimized.'); return; } location.reload(); } else { clickedBtn.disabled = false; clickedBtn.value = i18n.apply || 'Apply'; var failMsg = isBulkUnoptimize ? (i18n.bulkUnoptimizeFailed || 'Bulk unoptimize failed.') : (i18n.bulkFailed || 'Bulk optimization failed.'); alert(data.data || failMsg); } }) .catch(function() { clickedBtn.disabled = false; clickedBtn.value = i18n.apply || 'Apply'; }); }); } // ── Search Clear Button ── function initSearchClearBtn() { var searchInput = document.querySelector('.metasync-toolbar-right .search-box input[type="search"]'); if (!searchInput) return; var wrapper = document.createElement('div'); wrapper.className = 'metasync-search-input-wrap'; searchInput.parentNode.insertBefore(wrapper, searchInput); wrapper.appendChild(searchInput); var clearBtn = document.createElement('button'); clearBtn.type = 'button'; clearBtn.className = 'metasync-clear-search'; clearBtn.innerHTML = '×'; clearBtn.setAttribute('aria-label', 'Clear search'); wrapper.appendChild(clearBtn); function updateVisibility() { if (searchInput.value.length > 0) { clearBtn.classList.add('visible'); } else { clearBtn.classList.remove('visible'); } } searchInput.addEventListener('input', updateVisibility); updateVisibility(); clearBtn.addEventListener('click', function() { searchInput.value = ''; updateVisibility(); var form = searchInput.closest('form'); if (form) { form.submit(); } }); } initSearchClearBtn(); })();