PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.84
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.84
51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 All 40 releases
king-addons / includes / extensions / Image_Optimizer / assets / attachment-details.js

attachment-details.js in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.84, at includes/extensions/Image_Optimizer/assets/attachment-details.js

817 lines 24.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* global jQuery, kingImageOptimizerAttachment */
2 (function ($) {
3 'use strict';
4 // Prevent accidental tab close/navigation during upload-auto optimization
5 let unloadGuardEnabled = false;
6 function setUnloadGuard(enabled) {
7 unloadGuardEnabled = !!enabled;
8 }
9
10 window.addEventListener('beforeunload', function (e) {
11 if (!unloadGuardEnabled) return;
12 e.preventDefault();
13 e.returnValue = 'A process is running. Are you sure you want to leave this page?';
14 return e.returnValue;
15 });
16
17 // Toast UI (simple)
18 let toastHideTimer = null;
19 function ensureToast() {
20 let $toast = $('#king-img-upload-toast');
21 if ($toast.length) return $toast;
22
23 $toast = $(
24 '<div id="king-img-upload-toast" class="king-img-toast" style="display:none;">' +
25 '<div class="king-img-toast-head">' +
26 '<span class="dashicons king-img-toast-icon dashicons-update" aria-hidden="true"></span>' +
27 '<div class="king-img-toast-title">Optimizing uploads</div>' +
28 '<button type="button" class="king-img-toast-close" aria-label="Close">&times;</button>' +
29 '</div>' +
30 '<div class="king-img-toast-body">' +
31 '<div class="king-img-toast-line" id="king-img-toast-line">Preparing...</div>' +
32 '<div class="king-img-toast-bar"><div class="king-img-toast-bar-fill" id="king-img-toast-bar-fill" style="width:0%"></div></div>' +
33 '<div class="king-img-toast-meta" id="king-img-toast-meta">0 / 0</div>' +
34 '</div>' +
35 '</div>'
36 );
37
38 $(document.body).append($toast);
39 $toast.on('click', '.king-img-toast-close', function () {
40 $toast.fadeOut(200);
41 });
42
43 return $toast;
44 }
45
46 function toastUpdate({ line, current, total, percent, status }) {
47 const $toast = ensureToast();
48 $toast.show();
49
50 if (toastHideTimer) {
51 window.clearTimeout(toastHideTimer);
52 toastHideTimer = null;
53 }
54
55 if (typeof line !== 'undefined') {
56 $('#king-img-toast-line').text(String(line));
57 }
58 if (typeof current !== 'undefined' && typeof total !== 'undefined') {
59 $('#king-img-toast-meta').text(String(current) + ' / ' + String(total));
60 }
61 if (typeof percent !== 'undefined') {
62 const p = Math.max(0, Math.min(100, Number(percent) || 0));
63 $('#king-img-toast-bar-fill').css('width', p + '%');
64 }
65
66 const $icon = $toast.find('.king-img-toast-icon');
67 $toast.removeClass('is-success is-error is-warning');
68 if (status === 'success') {
69 $toast.addClass('is-success');
70 $icon.removeClass().addClass('dashicons king-img-toast-icon dashicons-yes-alt');
71 toastHideTimer = window.setTimeout(function () { $toast.fadeOut(200); }, 5000);
72 } else if (status === 'error') {
73 $toast.addClass('is-error');
74 $icon.removeClass().addClass('dashicons king-img-toast-icon dashicons-dismiss');
75 toastHideTimer = window.setTimeout(function () { $toast.fadeOut(200); }, 5000);
76 } else if (status === 'warning') {
77 $toast.addClass('is-warning');
78 $icon.removeClass().addClass('dashicons king-img-toast-icon dashicons-warning');
79 toastHideTimer = window.setTimeout(function () { $toast.fadeOut(200); }, 5000);
80 } else {
81 $icon.removeClass().addClass('dashicons king-img-toast-icon dashicons-update');
82 }
83 }
84
85 function isMediaModalOpen() {
86 try {
87 return $('.media-modal:visible').length > 0;
88 } catch (e) {
89 return false;
90 }
91 }
92
93 async function refreshAttachmentOptimizerCard(attachmentId) {
94 const id = Number(attachmentId || 0);
95 if (!id) {
96 return;
97 }
98
99 try {
100 const res = await postAjax({
101 action: 'king_img_get_attachment_card_html',
102 attachment_id: id,
103 });
104
105 if (!res || !res.success || !res.data || !res.data.html) {
106 return;
107 }
108
109 const $cards = $('.king-img-attach-card[data-king-img-attachment-id="' + String(id) + '"]');
110 if ($cards.length) {
111 $cards.each(function () {
112 $(this).replaceWith(res.data.html);
113 });
114 }
115 } catch (e) {
116 // ignore
117 }
118 }
119
120 function getAllBlocksDeep(blocks) {
121 const out = [];
122 const walk = function (arr) {
123 (arr || []).forEach(function (b) {
124 if (!b) return;
125 out.push(b);
126 if (b.innerBlocks && b.innerBlocks.length) {
127 walk(b.innerBlocks);
128 }
129 });
130 };
131 walk(blocks || []);
132 return out;
133 }
134
135 async function refreshGutenbergImageBlocksForAttachment(attachmentId) {
136 const id = Number(attachmentId || 0);
137 if (!id) return;
138
139 if (!(window.wp && wp.data)) {
140 return;
141 }
142
143 // Only relevant in the block editor.
144 const blockEditor = wp.data.select('core/block-editor');
145 const blockEditorDispatch = wp.data.dispatch('core/block-editor');
146 if (!blockEditor || !blockEditorDispatch) {
147 return;
148 }
149
150 let optimizedUrl = '';
151
152 // Prefer REST (same source Gutenberg uses for entities).
153 try {
154 if (wp.apiFetch) {
155 const media = await wp.apiFetch({ path: '/wp/v2/media/' + String(id) });
156 if (media && media.source_url) {
157 optimizedUrl = String(media.source_url);
158 }
159 }
160 } catch (e) {
161 // ignore
162 }
163
164 // Fallback to wp.media model.
165 if (!optimizedUrl) {
166 try {
167 if (wp.media && wp.media.attachment) {
168 const model = wp.media.attachment(id);
169 // Ensure freshest data.
170 if (model && model.fetch) {
171 await new Promise((resolve) => {
172 model.fetch({
173 success: function () { resolve(); },
174 error: function () { resolve(); },
175 });
176 });
177 }
178 optimizedUrl = String((model && model.get && (model.get('url') || model.get('source_url'))) || '');
179 }
180 } catch (e) {
181 // ignore
182 }
183 }
184
185 if (!optimizedUrl) {
186 return;
187 }
188
189 // Update any core/image blocks using this attachment ID.
190 try {
191 const blocks = getAllBlocksDeep(blockEditor.getBlocks());
192 blocks.forEach(function (b) {
193 if (!b || !b.clientId) return;
194 if (b.name !== 'core/image') return;
195 const bid = Number((b.attributes && b.attributes.id) || 0);
196 if (bid !== id) return;
197
198 blockEditorDispatch.updateBlockAttributes(b.clientId, {
199 url: optimizedUrl,
200 });
201 });
202 } catch (e) {
203 // ignore
204 }
205 }
206
207 function postAjax(data) {
208 return $.ajax({
209 url: kingImageOptimizerAttachment.ajaxUrl,
210 method: 'POST',
211 dataType: 'json',
212 data: Object.assign({ nonce: kingImageOptimizerAttachment.nonce }, data),
213 });
214 }
215
216 async function refreshSettingsFromServer() {
217 try {
218 const res = await postAjax({
219 action: 'king_img_get_settings',
220 });
221 if (res && res.success && res.data && res.data.settings) {
222 kingImageOptimizerAttachment.settings = res.data.settings;
223 }
224 } catch (e) {
225 // ignore
226 }
227 }
228
229 function setBusy($card, busy) {
230 $card.toggleClass('is-busy', !!busy);
231 $card.find('.king-img-attachment-action').prop('disabled', !!busy);
232 }
233
234 function setMsg($card, msg) {
235 $card.find('.king-img-attach-msg').text(msg || '');
236 }
237
238 function dataUrlFromBlob(blob) {
239 return new Promise((resolve, reject) => {
240 const reader = new FileReader();
241 reader.onload = () => resolve(String(reader.result || ''));
242 reader.onerror = () => reject(new Error('Failed to read blob'));
243 reader.readAsDataURL(blob);
244 });
245 }
246
247 function loadImage(url) {
248 return new Promise((resolve, reject) => {
249 const img = new Image();
250 img.onload = () => resolve(img);
251 img.onerror = () => reject(new Error('Failed to load image'));
252 img.crossOrigin = 'anonymous';
253 img.src = url;
254 });
255 }
256
257 async function convertOneSizeToWebp(imageInfo, quality, resizeEnabled, maxWidth) {
258 const img = await loadImage(imageInfo.url);
259
260 const srcW = img.naturalWidth || imageInfo.width || 0;
261 const srcH = img.naturalHeight || imageInfo.height || 0;
262
263 let outW = srcW;
264 let outH = srcH;
265
266 const mw = Number(maxWidth || 0);
267 if (resizeEnabled && mw > 0 && srcW > mw) {
268 const scale = mw / srcW;
269 outW = mw;
270 outH = Math.max(1, Math.round(srcH * scale));
271 }
272
273 const canvas = document.createElement('canvas');
274 canvas.width = outW;
275 canvas.height = outH;
276
277 const ctx = canvas.getContext('2d', { alpha: true });
278 ctx.drawImage(img, 0, 0, outW, outH);
279
280 const blob = await new Promise((resolve, reject) => {
281 canvas.toBlob(
282 (b) => (b ? resolve(b) : reject(new Error('toBlob returned null'))),
283 'image/webp',
284 quality
285 );
286 });
287
288 const dataUrl = await dataUrlFromBlob(blob);
289
290 return {
291 dataUrl,
292 optimizedSize: blob.size,
293 originalSize: Number(imageInfo.filesize || 0),
294 };
295 }
296
297 async function handleConvert($card) {
298 const attachmentId = Number($card.data('king-img-attachment-id'));
299 await refreshSettingsFromServer();
300 const settings = kingImageOptimizerAttachment.settings || {};
301
302 const qualityPct = Number(settings.quality || 82);
303 const quality = Math.max(0.1, Math.min(1, qualityPct / 100));
304
305 const skipSmall = !!settings.skip_small;
306 const minSize = Number(settings.min_size || 10240);
307
308 const resizeEnabled = !!settings.resize_enabled;
309 const maxWidth = Number(settings.max_width || 2048);
310
311 setBusy($card, true);
312 setMsg($card, kingImageOptimizerAttachment.strings.fetching || 'Fetching image data...');
313
314 const res = await postAjax({
315 action: 'king_img_get_image_data',
316 attachment_id: attachmentId,
317 sizes: 'all',
318 });
319
320 if (!res || !res.success) {
321 throw new Error((res && res.data && res.data.message) || 'Failed to fetch image data');
322 }
323
324 const images = (res.data && res.data.images) || {};
325 const sizeNames = Object.keys(images);
326 if (!sizeNames.length) {
327 throw new Error('No image sizes found');
328 }
329
330 let converted = 0;
331 let skipped = 0;
332
333 for (let i = 0; i < sizeNames.length; i++) {
334 const size = sizeNames[i];
335 const info = images[size];
336
337 if (skipSmall && Number(info.filesize || 0) > 0 && Number(info.filesize || 0) < minSize) {
338 skipped++;
339 continue;
340 }
341
342 setMsg(
343 $card,
344 (kingImageOptimizerAttachment.strings.converting || 'Converting...') +
345 ' ' +
346 size +
347 ' (' +
348 String(i + 1) +
349 '/' +
350 String(sizeNames.length) +
351 ')'
352 );
353
354 const out = await convertOneSizeToWebp(info, quality, resizeEnabled, maxWidth);
355
356 const saveRes = await postAjax({
357 action: 'king_img_save_optimized',
358 attachment_id: attachmentId,
359 size: size,
360 format: 'webp',
361 method: 'canvas',
362 image_data: out.dataUrl,
363 original_size: out.originalSize,
364 optimized_size: out.optimizedSize,
365 });
366
367 if (!saveRes || !saveRes.success) {
368 if (saveRes && saveRes.data && saveRes.data.code === 'quota_exceeded') {
369 setMsg(
370 $card,
371 kingImageOptimizerAttachment.strings.quotaExceeded ||
372 'Free plan limit reached (200 optimizations/month). Upgrade to Unlimited to continue.'
373 );
374 setBusy($card, false);
375 return;
376 }
377 throw new Error((saveRes && saveRes.data && saveRes.data.message) || 'Failed to save optimized image');
378 }
379
380 converted++;
381 }
382
383 if (converted === 0 && skipped > 0) {
384 // Optional: persist skipped state so it doesn't show as pending elsewhere.
385 await postAjax({
386 action: 'king_img_mark_skipped',
387 attachment_id: attachmentId,
388 reason: 'too_small',
389 }).catch(() => {});
390
391 setMsg($card, kingImageOptimizerAttachment.strings.skipped || 'Skipped (too small)');
392 setBusy($card, false);
393 return;
394 }
395
396 if (isMediaModalOpen()) {
397 setMsg($card, kingImageOptimizerAttachment.strings.optimized || 'Optimized');
398 await refreshAttachmentOptimizerCard(attachmentId);
399 await refreshGutenbergImageBlocksForAttachment(attachmentId);
400 try {
401 if (window.wp && wp.media && wp.media.attachment) {
402 wp.media.attachment(attachmentId).fetch();
403 }
404 } catch (e) {
405 // ignore
406 }
407 setBusy($card, false);
408 return;
409 }
410
411 setMsg($card, kingImageOptimizerAttachment.strings.doneReload || 'Done. Reloading...');
412 window.location.reload();
413 }
414
415 async function handleRestore($card) {
416 const attachmentId = Number($card.data('king-img-attachment-id'));
417
418 const ok = window.confirm(
419 kingImageOptimizerAttachment.strings.confirmRestore || 'Restore original and delete optimized files?'
420 );
421 if (!ok) return;
422
423 setBusy($card, true);
424 setMsg($card, kingImageOptimizerAttachment.strings.restoring || 'Restoring...');
425
426 const res = await postAjax({
427 action: 'king_img_full_restore',
428 attachment_id: attachmentId,
429 });
430
431 if (!res || !res.success) {
432 throw new Error((res && res.data && res.data.message) || 'Restore failed');
433 }
434
435 if (isMediaModalOpen()) {
436 await refreshAttachmentOptimizerCard(attachmentId);
437 await refreshGutenbergImageBlocksForAttachment(attachmentId);
438 try {
439 if (window.wp && wp.media && wp.media.attachment) {
440 wp.media.attachment(attachmentId).fetch();
441 }
442 } catch (e) {
443 // ignore
444 }
445 setBusy($card, false);
446 setMsg($card, kingImageOptimizerAttachment.strings.restored || 'Restored');
447 return;
448 }
449
450 setMsg($card, kingImageOptimizerAttachment.strings.doneReload || 'Done. Reloading...');
451 window.location.reload();
452 }
453
454 $(document).on('click', '.king-img-attachment-action', async function () {
455 const $btn = $(this);
456 const $card = $btn.closest('.king-img-attach-card');
457 const action = String($btn.data('king-img-action') || '');
458
459 try {
460 if (action === 'restore') {
461 await handleRestore($card);
462 } else {
463 await handleConvert($card);
464 }
465 } catch (e) {
466 setBusy($card, false);
467 setMsg($card, (kingImageOptimizerAttachment.strings.errorPrefix || 'Error: ') + (e && e.message ? e.message : String(e)));
468 }
469 });
470
471 // --- Auto optimize new uploads (browser) ---
472 const uploadAuto = {
473 enabled: !!(kingImageOptimizerAttachment && kingImageOptimizerAttachment.settings && kingImageOptimizerAttachment.settings.auto_optimize_uploads),
474 queue: [],
475 queuedIds: new Set(),
476 processedIds: new Set(),
477 bound: false,
478 running: false,
479 total: 0,
480 done: 0,
481 errors: 0,
482 };
483
484 async function convertAttachmentId(attachmentId, label) {
485 const settings = kingImageOptimizerAttachment.settings || {};
486
487 const qualityPct = Number(settings.quality || 82);
488 const quality = Math.max(0.1, Math.min(1, qualityPct / 100));
489
490 const skipSmall = !!settings.skip_small;
491 const minSize = Number(settings.min_size || 10240);
492
493 const resizeEnabled = !!settings.resize_enabled;
494 const maxWidth = Number(settings.max_width || 2048);
495
496 const autoReplaceUrls = settings.auto_replace_urls !== false;
497
498 const res = await postAjax({
499 action: 'king_img_get_image_data',
500 attachment_id: attachmentId,
501 sizes: 'all',
502 });
503
504 if (!res || !res.success) {
505 throw new Error((res && res.data && res.data.message) || 'Failed to fetch image data');
506 }
507
508 const images = (res.data && res.data.images) || {};
509 const sizeNames = Object.keys(images);
510 if (!sizeNames.length) {
511 throw new Error('No image sizes found');
512 }
513
514 let converted = 0;
515 let skipped = 0;
516
517 for (let i = 0; i < sizeNames.length; i++) {
518 const size = sizeNames[i];
519 const info = images[size];
520
521 if (skipSmall && Number(info.filesize || 0) > 0 && Number(info.filesize || 0) < minSize) {
522 skipped++;
523 continue;
524 }
525
526 toastUpdate({
527 line: `Converting ${label} • ${size} (${i + 1}/${sizeNames.length})`,
528 });
529
530 const out = await convertOneSizeToWebp(info, quality, resizeEnabled, maxWidth);
531
532 const saveRes = await postAjax({
533 action: 'king_img_save_optimized',
534 attachment_id: attachmentId,
535 size: size,
536 format: 'webp',
537 method: 'canvas',
538 image_data: out.dataUrl,
539 original_size: out.originalSize,
540 optimized_size: out.optimizedSize,
541 });
542
543 if (!saveRes || !saveRes.success) {
544 if (saveRes && saveRes.data && saveRes.data.code === 'quota_exceeded') {
545 setMsg(
546 $card,
547 kingImageOptimizerAttachment.strings.quotaExceeded ||
548 'Free plan limit reached (200 optimizations/month). Upgrade to Unlimited to continue.'
549 );
550 setBusy($card, false);
551 return;
552 }
553 throw new Error((saveRes && saveRes.data && saveRes.data.message) || 'Failed to save optimized image');
554 }
555
556 converted++;
557 }
558
559 if (converted === 0 && skipped > 0) {
560 // Persist skipped state so it doesn't remain pending elsewhere.
561 await postAjax({
562 action: 'king_img_mark_skipped',
563 attachment_id: attachmentId,
564 reason: 'too_small',
565 }).catch(() => {});
566 return { status: 'skipped' };
567 }
568
569 // Optionally apply URL replacements if enabled
570 if (autoReplaceUrls) {
571 await postAjax({
572 action: 'king_img_apply_webp_urls',
573 attachment_id: attachmentId,
574 }).catch(() => {});
575 }
576
577 // Refresh the attachment model in media library if available
578 try {
579 if (window.wp && wp.media && wp.media.attachment) {
580 wp.media.attachment(attachmentId).fetch();
581 }
582 } catch (e) {
583 // ignore
584 }
585
586 // Refresh the card in the modal/details UI if present
587 await refreshAttachmentOptimizerCard(attachmentId);
588
589 // Refresh Gutenberg block previews that reference this attachment
590 await refreshGutenbergImageBlocksForAttachment(attachmentId);
591
592 return { status: 'optimized' };
593 }
594
595 function enqueueUploadedImage(attachmentId, label) {
596 const id = Number(attachmentId || 0);
597 if (!id) return;
598 if (uploadAuto.processedIds.has(id) || uploadAuto.queuedIds.has(id)) return;
599
600 uploadAuto.queue.push({ id, label: String(label || ('Attachment #' + id)) });
601 uploadAuto.queuedIds.add(id);
602 uploadAuto.total++;
603
604 if (!uploadAuto.running) {
605 processUploadQueue();
606 } else {
607 toastUpdate({ line: 'Added to queue: ' + String(label || id), current: uploadAuto.done, total: uploadAuto.total });
608 }
609 }
610
611 async function processUploadQueue() {
612 if (uploadAuto.running) return;
613 if (!uploadAuto.enabled) return;
614 if (!uploadAuto.queue.length) return;
615
616 uploadAuto.running = true;
617 setUnloadGuard(true);
618
619 // Ensure we use the latest saved settings (Default Quality, skip-small, etc)
620 await refreshSettingsFromServer();
621
622 toastUpdate({
623 line: 'Starting…',
624 current: uploadAuto.done,
625 total: uploadAuto.total,
626 percent: 0,
627 status: 'running',
628 });
629
630 while (uploadAuto.queue.length) {
631 const item = uploadAuto.queue.shift();
632 const id = item.id;
633
634 const currentIndex = uploadAuto.done + 1;
635 const total = Math.max(uploadAuto.total, currentIndex);
636 const percent = Math.round(((currentIndex - 1) / total) * 100);
637 toastUpdate({ line: `Optimizing ${item.label}`, current: currentIndex - 1, total, percent });
638
639 try {
640 await convertAttachmentId(id, item.label);
641 uploadAuto.processedIds.add(id);
642 } catch (e) {
643 uploadAuto.errors++;
644 }
645
646 uploadAuto.done++;
647 const p2 = Math.round((uploadAuto.done / uploadAuto.total) * 100);
648 toastUpdate({ current: uploadAuto.done, total: uploadAuto.total, percent: p2 });
649 }
650
651 // Done
652 setUnloadGuard(false);
653 uploadAuto.running = false;
654
655 if (uploadAuto.errors > 0) {
656 toastUpdate({
657 line: `Done with ${uploadAuto.errors} error(s).`,
658 current: uploadAuto.done,
659 total: uploadAuto.total,
660 percent: 100,
661 status: 'warning',
662 });
663 } else {
664 toastUpdate({
665 line: 'Done. All uploads optimized.',
666 current: uploadAuto.done,
667 total: uploadAuto.total,
668 percent: 100,
669 status: 'success',
670 });
671 }
672 }
673
674 function initAutoOptimizeUploads() {
675 if (!uploadAuto.enabled) return false;
676 if (uploadAuto.bound) return true;
677
678 // Hook into WP media attachments collection: new uploads get added here.
679 try {
680 if (window.wp && wp.media && wp.media.model && wp.media.model.Attachments && wp.media.model.Attachments.all) {
681 const all = wp.media.model.Attachments.all;
682
683 // Snapshot existing IDs so we don't auto-optimize the whole library on initial population.
684 const knownIds = new Set();
685 try {
686 const existingModels = all.models || [];
687 for (let i = 0; i < existingModels.length; i++) {
688 const m = existingModels[i];
689 if (m && m.get) {
690 const id0 = Number(m.get('id') || 0);
691 if (id0) knownIds.add(id0);
692 }
693 }
694 } catch (e) {
695 // ignore
696 }
697
698 all.on('add', function (model) {
699 try {
700 if (!model || !model.get) return;
701 const type = model.get('type');
702 if (type !== 'image') return;
703
704 const maybeEnqueue = function () {
705 const id = Number(model.get('id') || 0);
706 if (!id) return;
707 if (knownIds.has(id)) return;
708 knownIds.add(id);
709 const filename = model.get('filename') || model.get('title') || ('Attachment #' + id);
710 enqueueUploadedImage(id, filename);
711 };
712
713 // Some upload models receive their attachment ID after upload completes.
714 maybeEnqueue();
715 if (!Number(model.get('id') || 0)) {
716 if (model.once) {
717 model.once('change:id', maybeEnqueue);
718 model.once('sync', maybeEnqueue);
719 }
720 }
721 } catch (e) {
722 // ignore
723 }
724 });
725
726 uploadAuto.bound = true;
727 return true;
728 }
729 } catch (e) {
730 // ignore
731 }
732
733 return false;
734 }
735
736 function initApiFetchMediaUploadHook() {
737 if (!uploadAuto.enabled) return;
738 if (!(window.wp && wp.apiFetch && typeof wp.apiFetch.use === 'function')) return;
739 if (wp.apiFetch.__kingImgMediaUploadHooked) return;
740
741 wp.apiFetch.__kingImgMediaUploadHooked = true;
742
743 // Gutenberg/Image block direct uploads use wp.apiFetch POST /wp/v2/media
744 wp.apiFetch.use(function (options, next) {
745 const opts = options || {};
746 const method = String(opts.method || 'GET').toUpperCase();
747 const path = String(opts.path || opts.url || '');
748 const isMediaUpload = method === 'POST' && path.indexOf('/wp/v2/media') !== -1;
749
750 let labelFromFile = '';
751 if (isMediaUpload) {
752 try {
753 const body = opts.body;
754 if (body && typeof body.get === 'function') {
755 const file = body.get('file');
756 if (file && file.name) {
757 labelFromFile = String(file.name);
758 }
759 }
760 } catch (e) {
761 // ignore
762 }
763 }
764
765 return next(options).then(function (result) {
766 try {
767 if (!isMediaUpload) {
768 return result;
769 }
770
771 const id = Number(result && result.id ? result.id : 0);
772 if (!id) {
773 return result;
774 }
775
776 const mediaType = String((result && result.media_type) || '');
777 const mimeType = String((result && result.mime_type) || '');
778 const isImage = mediaType === 'image' || mimeType.indexOf('image/') === 0;
779 if (!isImage) {
780 return result;
781 }
782
783 const titleObj = result && result.title ? result.title : null;
784 const title = titleObj && (titleObj.raw || titleObj.rendered) ? (titleObj.raw || titleObj.rendered) : '';
785 enqueueUploadedImage(id, labelFromFile || title || ('Attachment #' + id));
786 } catch (e) {
787 // ignore
788 }
789
790 return result;
791 });
792 });
793 }
794
795 function scheduleAutoOptimizeUploadsInit() {
796 if (!uploadAuto.enabled) return;
797 if (uploadAuto.bound) return;
798
799 // In the "Select or Upload Media" modal, wp.media collections are created lazily.
800 // Retry a few times until wp.media.model.Attachments.all exists.
801 let tries = 0;
802 const timer = window.setInterval(function () {
803 tries++;
804 const ok = initAutoOptimizeUploads();
805 if (ok || tries >= 120) {
806 window.clearInterval(timer);
807 }
808 }, 500);
809 }
810
811 $(document).ready(function () {
812 initAutoOptimizeUploads();
813 scheduleAutoOptimizeUploadsInit();
814 initApiFetchMediaUploadHook();
815 });
816 })(jQuery);
817