PluginProbe
Squeeze – Image Optimization & Compression, WEBP Conversion / 1.4.5
Squeeze – Image Optimization & Compression, WEBP Conversion v1.4.5
1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 trunk 1.0 1.1 1.2 1.3 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5 1.5.1 1.5.2 1.6 All 42 releases
squeeze / assets / js / script.js

script.js in Squeeze – Image Optimization & Compression, WEBP Conversion 1.4.5, at assets/js/script.js

585 lines 18.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import * as avif from '@jsquash/avif';
2 import * as webp from '@jsquash/webp';
3 import * as jpeg from '@jsquash/jpeg';
4 import * as png from '@jsquash/png';
5 import optimise from '@jsquash/oxipng/optimise';
6 const { __ } = wp.i18n; // Import __() from wp.i18n
7
8 (function () {
9
10 const bulkBtn = document.querySelector("input[name='squeeze_bulk']")
11 const bulkAgainBtn = document.querySelector("input[name='squeeze_bulk_again']")
12 const bulkPathBtn = document.querySelector("input[name='squeeze_bulk_path_button']")
13 const squeeze_bulk_ids = document.querySelector("input[name='squeeze_bulk_ids']")?.value ?? null;
14 const squeeze_bulk_all_ids = document.querySelector("input[name='squeeze_bulk_all_ids']")?.value ?? null;
15 const uncompressedIDs = squeeze_bulk_ids ? squeeze_bulk_ids.split(",") : [];
16 const allIDs = squeeze_bulk_all_ids ? squeeze_bulk_all_ids.split(",") : [];
17 let bulkPathData = [];
18
19 async function decode(sourceType, fileBuffer) {
20 switch (sourceType) {
21 case 'avif':
22 return await avif.decode(fileBuffer);
23 case 'jpeg':
24 return await jpeg.decode(fileBuffer);
25 case 'png':
26 return await png.decode(fileBuffer);
27 case 'webp':
28 return await webp.decode(fileBuffer);
29 default:
30 throw new Error(`Unknown source type: ${sourceType}`);
31 }
32 }
33
34 async function encode(outputType, imageData) {
35 const options = JSON.parse(squeeze.options);
36
37 try {
38 switch (outputType) {
39 case 'avif':
40 const avifOptions = {}
41 for (const [key, value] of Object.entries(options)) {
42 if (key.includes('avif')) {
43 const keyName = key.replace('avif_', '')
44 avifOptions[keyName] = value
45 }
46 }
47 return await avif.encode(imageData, avifOptions);
48 case 'jpeg':
49 const jpegOptions = {}
50 for (const [key, value] of Object.entries(options)) {
51 if (key.includes('jpeg')) {
52 const keyName = key.replace('jpeg_', '')
53 jpegOptions[keyName] = value
54 }
55 }
56 return await jpeg.encode(imageData, jpegOptions);
57 case 'png':
58 const pngOptions = {}
59 for (const [key, value] of Object.entries(options)) {
60 if (key.includes('png')) {
61 const keyName = key.replace('png_', '')
62 pngOptions[keyName] = value
63 }
64 }
65 return await png.encode(imageData, pngOptions);
66 case 'webp':
67 const webpOptions = {}
68 for (const [key, value] of Object.entries(options)) {
69 if (key.includes('webp')) {
70 const keyName = key.replace('webp_', '')
71 webpOptions[keyName] = value
72 }
73 }
74 return await webp.encode(imageData, webpOptions);
75 default:
76 throw new Error(`Unknown output type: ${outputType}`);
77 }
78 } catch (error) {
79 console.error(error)
80 return false;
81 }
82
83 }
84
85 async function convert(sourceType, outputType, fileBuffer) {
86 const imageData = await decode(sourceType, fileBuffer);
87 return encode(outputType, imageData);
88 }
89
90 function blobToBase64(blob) {
91 return new Promise((resolve, _) => {
92 const reader = new FileReader();
93 reader.onloadend = () => resolve(reader.result);
94 reader.readAsDataURL(blob);
95 });
96 }
97
98 async function showOutput(imageBuffer, outputType) {
99 if (!imageBuffer) {
100 return false;
101 }
102 const imageBlob = new Blob([imageBuffer], { type: `image/${outputType}` });
103 const base64String = await blobToBase64(imageBlob);
104
105 return base64String;
106 }
107
108 const compressJPEG = async ({url, name, sourceType, outputType, mime}) => {
109 let response = await fetch(url);
110 let blob = await response.blob();
111 let metadata = {
112 type: mime
113 };
114 let imageObj = new File([blob], name, metadata);
115 const fileBuffer = await imageObj.arrayBuffer();
116 const imageBuffer = await convert(sourceType, outputType, fileBuffer);
117 const base64 = await showOutput(imageBuffer, outputType);
118 return base64
119 }
120
121 const compressPNG = async ({url, options, outputType}) => {
122 const pngOptions = {}
123 for (const [key, value] of Object.entries(options)) {
124 if (key.includes('png')) {
125 const keyName = key.replace('png_', '')
126 pngOptions[keyName] = value
127 }
128 }
129 const imageBuffer = await fetch(url).then(res => res.arrayBuffer()).then(pngImageBuffer => optimise(pngImageBuffer, pngOptions));
130 const base64 = await showOutput(imageBuffer, outputType);
131 return base64
132 }
133
134 const compressWEBP = async ({url, name, sourceType, outputType, mime}) => {
135 let webpResponse = await fetch(url);
136 let webpBlob = await webpResponse.blob();
137 let webpMetadata = {
138 type: mime
139 };
140 let webpImageObj = new File([webpBlob], name, webpMetadata);
141 const fileBuffer = await webpImageObj.arrayBuffer();
142 const imageBuffer = await convert(sourceType, outputType, fileBuffer);
143 const base64 = await showOutput(imageBuffer, outputType);
144 return base64
145 }
146
147 const compressAVIF = async ({url, name, sourceType, outputType, mime}) => {
148 let avifResponse = await fetch(url);
149 let avifBlob = await avifResponse.blob();
150 let avifMetadata = {
151 type: mime
152 };
153 let avifImageObj = new File([avifBlob], name, avifMetadata);
154 const fileBuffer = await avifImageObj.arrayBuffer();
155 const imageBuffer = await convert(sourceType, outputType, fileBuffer);
156 const base64 = await showOutput(imageBuffer, outputType);
157 return base64
158 }
159
160 async function handleUpload({ attachment, isBulk = false, target = null, type = 'uncompressed' }) {
161 const attachmentData = attachment.attributes;
162 const url = attachmentData?.originalImageURL ?? attachmentData.url;
163 const mime = attachmentData.mime;
164 const name = attachmentData.name;
165 const filename = attachmentData?.originalImageName ?? attachmentData.filename;
166 const attachmentID = attachmentData.id;
167 const format = mime.split("/")[1];
168 const sourceType = format;
169 const outputType = format;
170 const options = JSON.parse(squeeze.options);
171 const sizes = attachmentData.sizes;
172 const compressThumbs = options.compress_thumbs;
173
174 //console.log(attachmentData, 'attachmentData')
175
176 let base64;
177 let base64Sizes = {};
178
179 async function compressAndAssign(compressFunction, url, name, sourceType, outputType, mime, sizes, compressThumbs, base64Sizes) {
180 let base64;
181
182 if (compressFunction === compressPNG) {
183 base64 = await compressFunction({url, options, outputType});
184 } else {
185 base64 = await compressFunction({url, name, sourceType, outputType, mime});
186 }
187
188 if (!sizes) {
189 return base64;
190 }
191
192 for (const [key, value] of Object.entries(sizes)) {
193 if (!(key in compressThumbs)) {
194 continue;
195 }
196
197 const sizeURL = value.url;
198 const sizeWidth = value.width;
199 const sizeHeight = value.height;
200 const sizeName = `${name}-${sizeWidth}x${sizeHeight}`;
201 let sizeBase64;
202
203 if (compressFunction === compressPNG) {
204 sizeBase64 = await compressFunction({url: sizeURL, options, outputType});
205 } else {
206 sizeBase64 = await compressFunction({url: sizeURL, name: sizeName, sourceType, outputType, mime});
207 }
208
209 Object.assign(base64Sizes, { [key]: {'url': sizeURL, 'base64': sizeBase64} });
210 }
211 return base64;
212 }
213
214 switch (format) {
215 case 'avif':
216 base64 = await compressAndAssign(compressAVIF, url, name, sourceType, outputType, mime, sizes, compressThumbs, base64Sizes);
217 break;
218 case 'jpeg':
219 base64 = await compressAndAssign(compressJPEG, url, name, sourceType, outputType, mime, sizes, compressThumbs, base64Sizes);
220 break;
221 case 'png':
222 base64 = await compressAndAssign(compressPNG, url, name, sourceType, outputType, mime, sizes, compressThumbs, base64Sizes);
223 break;
224 case 'webp':
225 base64 = await compressAndAssign(compressWEBP, url, name, sourceType, outputType, mime, sizes, compressThumbs, base64Sizes);
226 break;
227 }
228
229 if (!base64) {
230
231 if (isBulk) {
232 logMsg(__('An error has occured. Check the console for details.', 'squeeze'))
233 logMsg(`===============================\r\n`)
234 handleBulkUpload(type)
235 } else {
236 if (target) {
237 target.closest("td").querySelector(".squeeze_status").innerHTML = __('An error has occured. Check the console for details.', 'squeeze')
238 target.remove();
239 }
240 }
241
242 return;
243 }
244
245 let data = {
246 action: 'squeeze_update_attachment',
247 _ajax_nonce: squeeze.nonce,
248 filename: filename,
249 type: 'image',
250 format: format,
251 base64: base64,
252 base64Sizes: base64Sizes,
253 attachmentID: attachmentID,
254 url: url,
255 process: type,
256 }
257
258 jQuery.ajax({
259 url: squeeze.ajaxUrl,
260 type: 'POST',
261 data: data,
262 beforeSend: function () {
263 console.log(data, 'squeeze data')
264 if (isBulk) {
265 logMsg(`#${attachmentID}: ` + __('Compressed successfully, updating...', 'squeeze'))
266 }
267 },
268 error: function (error) {
269 console.error(error)
270 if (target) {
271 target.closest("td").querySelector(".squeeze_status").innerHTML = __('An error has occured. Check the console for details.', 'squeeze')
272 target.remove();
273 }
274 },
275 success: function (response) {
276 if (isBulk) {
277 if (response.success) {
278 logMsg(`#${attachmentID}: ` + __('Updated successfully', 'squeeze') + `\r\n===============================\r\n`);
279 } else {
280 logMsg(`#${attachmentID}: ` + response.data + `\r\n===============================\r\n`);
281 }
282 handleBulkUpload(type) // continue bulk process
283 }
284 if (!isBulk && target) { // on single attachment compress
285 target.closest("td").querySelector(".squeeze_status").innerHTML = response.data;
286 target.remove();
287 }
288 if (!target && !isBulk) { // on upload process
289 attachment.set('uploading', false) // resume uploading process
290 }
291 }
292 });
293
294 }
295
296 const handleBulkUpload = (type = 'uncompressed') => {
297 let currentID;
298 switch (type) {
299 case 'uncompressed':
300 currentID = uncompressedIDs[0];
301 break;
302 case 'all':
303 currentID = allIDs[0];
304 break;
305 case 'path':
306 currentID = bulkPathData[0]?.filename;
307 break;
308 default:
309 currentID = 0;
310 break;
311 }
312 const data = {
313 action: 'squeeze_get_attachment',
314 _ajax_nonce: squeeze.nonce,
315 attachmentID: currentID,
316 }
317
318 if (type === 'uncompressed') {
319 if (uncompressedIDs.length === 0) {
320 alert(__('All images have been compressed!', 'squeeze'))
321 restoreBulkButtons()
322 location.reload();
323 return;
324 }
325 } else if (type === 'all') {
326 if (allIDs.length === 0) {
327 alert(__('All images have been re-compressed again!', 'squeeze'))
328 restoreBulkButtons()
329 //location.reload();
330 return;
331 }
332 } else if (type === 'path') {
333 if (bulkPathData.length === 0) {
334 alert(__('All images have been compressed!', 'squeeze'))
335 restoreBulkButtons()
336 //location.reload();
337 return;
338 }
339 }
340
341 logMsg(`attachment #${currentID}: start compressing...`)
342
343 if (type === 'path') {
344
345 const attachment = {
346 attributes: {
347 url: bulkPathData[0].url,
348 mime: bulkPathData[0].mime,
349 name: bulkPathData[0].name,
350 filename: bulkPathData[0].filename,
351 id: bulkPathData[0].id,
352 }
353 }
354 bulkPathData.shift();
355 handleUpload({ attachment, isBulk: true, type: type })
356
357 } else {
358
359 jQuery.ajax({
360 url: squeeze.ajaxUrl,
361 type: 'POST',
362 data: data,
363 error: function (error) {
364 console.error(error)
365 },
366 success: function (response) {
367 if (response.success) {
368 const responseData = response.data;
369 const attachment = {
370 attributes: {
371 url: responseData.url,
372 mime: responseData.mime,
373 name: responseData.name,
374 filename: responseData.filename,
375 id: responseData.id,
376 }
377 }
378
379 if (type === 'uncompressed') {
380 uncompressedIDs.shift();
381 } else if (type === 'all') {
382 allIDs.shift();
383 }
384 handleUpload({ attachment, isBulk: true, type: type })
385 } else {
386 console.error(response.data)
387 }
388 }
389 });
390
391 }
392 }
393
394 function handleRestore(attachmentID, target) {
395 let data = {
396 action: 'squeeze_restore_attachment',
397 _ajax_nonce: squeeze.nonce,
398 attachmentID: attachmentID,
399 }
400
401 jQuery.ajax({
402 url: squeeze.ajaxUrl,
403 type: 'POST',
404 data: data,
405 beforeSend: function () {
406 target.disabled = true;
407 target.innerText = __('Restore in process...', 'squeeze')
408 },
409 error: function (error) {
410 console.error(error)
411 target.closest("td").querySelector(".squeeze_status").innerHTML = __('An error has occured. Check the console for details.', 'squeeze')
412 target.remove();
413 },
414 success: function (response) {
415 target.closest("td").querySelector(".squeeze_status").innerHTML = response.data; //__('Restored successfully', 'squeeze')
416 target.remove();
417 }
418 });
419 }
420
421 // Handle single compress button click
422 const handleSingleBtnClick = (event) => {
423 const attachmentID = event.target.dataset.attachment;
424
425 wp?.media?.attachment(attachmentID).fetch().then(function (data) {
426 const attachment = {
427 attributes: data
428 }
429 handleUpload({ attachment, target: event.target })
430 });
431 }
432
433 // Handle restore button click
434 const handleRestoreBtnClick = (event) => {
435 const attachmentID = event.target.dataset.attachment;
436 handleRestore(attachmentID, event.target)
437 }
438
439 // Handle bulk path button click
440 const handlePathUpload = (path) => {
441
442 const data = {
443 action: 'squeeze_get_attachment_by_path',
444 path: path,
445 _ajax_nonce: squeeze.nonce,
446 }
447
448 jQuery.ajax({
449 url: squeeze.ajaxUrl,
450 type: 'POST',
451 data: data,
452 error: function (error) {
453 console.error(error)
454 },
455 success: function (response) {
456 if (response.success) {
457 const responseData = response.data;
458 bulkPathData = responseData;
459 handleBulkUpload('path')
460 } else {
461 console.error(response.data)
462 logMsg(response.data)
463 restoreBulkButtons()
464 }
465 }
466 });
467 }
468
469 /**
470 * Handle single buttons click
471 */
472 function handleSingleButtonsClick() {
473 document.addEventListener("click", (e) => {
474 //console.log(e.target, 'e.target')
475 const singleBtnName = 'squeeze_compress_single';
476 const compressAgainBtnName = 'squeeze_compress_again';
477 const restoreBtnName = 'squeeze_restore';
478 if (e.target.getAttribute("name") === singleBtnName || e.target.getAttribute("name") === compressAgainBtnName) {
479 e.target.disabled = true;
480
481 if (e.target.getAttribute("name") === compressAgainBtnName) {
482 e.target.closest('.field').querySelector(`[name='${restoreBtnName}']`).disabled = true;
483 }
484
485 e.target.innerText = __('Compressing...', 'squeeze')
486 handleSingleBtnClick(e)
487 }
488 if (e.target.getAttribute("name") === restoreBtnName) {
489 e.target.disabled = true;
490 e.target.closest('td').querySelector(`[name='${compressAgainBtnName}']`).disabled = true;
491 handleRestoreBtnClick(e)
492 }
493 })
494 }
495
496 handleSingleButtonsClick()
497
498 function logMsg(msg) {
499 const bulkLogInput = document.querySelector("[name='squeeze_bulk_log']")
500 bulkLogInput.value += msg + `\r\n`;
501 }
502
503 function restoreBulkButtons() {
504 bulkBtn.disabled = false;
505 bulkAgainBtn.disabled = false;
506 bulkPathBtn.disabled = false;
507 window.onbeforeunload = null;
508 }
509
510 /**
511 * Handle bulk button click
512 */
513 bulkBtn?.addEventListener("click", (event) => {
514 if (uncompressedIDs.length === 0) {
515 return;
516 }
517
518 bulkBtn.disabled = true;
519 bulkAgainBtn.disabled = true;
520 bulkPathBtn.disabled = true;
521 handleBulkUpload('uncompressed')
522 window.onbeforeunload = handleOnLeave;
523 })
524
525 /**
526 * Handle bulk again button click
527 */
528 bulkAgainBtn?.addEventListener("click", (event) => {
529 bulkBtn.disabled = true;
530 bulkAgainBtn.disabled = true;
531 bulkPathBtn.disabled = true;
532 handleBulkUpload('all')
533 window.onbeforeunload = handleOnLeave;
534 })
535
536 /**
537 * Handle bulk path button click
538 */
539 bulkPathBtn?.addEventListener("click", (event) => {
540 const path = document.querySelector("input[name='squeeze_bulk_path']").value;
541
542 if (!path) {
543 alert(__('Please enter a valid path!', 'squeeze'))
544 return;
545 }
546
547 bulkBtn.disabled = true;
548 bulkAgainBtn.disabled = true;
549 bulkPathBtn.disabled = true;
550 handlePathUpload(path)
551 window.onbeforeunload = handleOnLeave;
552 })
553
554 // https://wordpress.stackexchange.com/a/131295/186146 - override wp.Uploader.prototype.success
555 jQuery.extend(wp?.Uploader?.prototype, {
556 success: function (attachment) {
557 //console.log(attachment, 'success');
558 const options = JSON.parse(squeeze.options);
559 const isAutoCompress = options.auto_compress;
560 const allowedMimeTypes = ['jpeg', 'png', 'webp', 'avif'];
561 let isImage = attachment.attributes.type === 'image' && allowedMimeTypes.includes(attachment.attributes.subtype)
562
563 if (isImage && isAutoCompress) {
564 // set 'uploading' param to true, to pause the uploading process
565 attachment.set('uploading', true)
566 handleUpload({ attachment })
567 }
568 },
569 });
570
571 /**
572 * Hadnle warning on page leave
573 */
574 function handleOnLeave() {
575 const urlParams = new URLSearchParams(window.location.search);
576 const page = urlParams.get('page');
577
578 if (page === 'squeeze-bulk') {
579 return __('Are you sure you want to leave this page? The compression process will be terminated!', 'squeeze');
580 }
581 };
582
583 })();
584
585 //console.log(JSON.parse(squeeze.options), 'squeeze.options')