Changeset 3346520
- Timestamp:
- 08/18/2025 03:03:50 PM (6 months ago)
- Location:
- squeeze
- Files:
-
- 94 added
- 5 edited
-
tags/1.7.1 (added)
-
tags/1.7.1/assets (added)
-
tags/1.7.1/assets/css (added)
-
tags/1.7.1/assets/css/admin.css (added)
-
tags/1.7.1/assets/css/admin.css.map (added)
-
tags/1.7.1/assets/css/admin.less (added)
-
tags/1.7.1/assets/images (added)
-
tags/1.7.1/assets/images/computer-folder-close-icon.svg (added)
-
tags/1.7.1/assets/images/computer-folder-open-icon.svg (added)
-
tags/1.7.1/assets/images/icon-bulk-page.svg (added)
-
tags/1.7.1/assets/images/icon-compare.svg (added)
-
tags/1.7.1/assets/images/icon-exclude.svg (added)
-
tags/1.7.1/assets/images/icon-resize.svg (added)
-
tags/1.7.1/assets/images/icon.svg (added)
-
tags/1.7.1/assets/images/replace_urls.jpg (added)
-
tags/1.7.1/assets/images/sprite.svg (added)
-
tags/1.7.1/assets/js (added)
-
tags/1.7.1/assets/js/0.bundle.js (added)
-
tags/1.7.1/assets/js/1.bundle.js (added)
-
tags/1.7.1/assets/js/1dbcc374dc53cb692541.wasm (added)
-
tags/1.7.1/assets/js/2.bundle.js (added)
-
tags/1.7.1/assets/js/3.bundle.js (added)
-
tags/1.7.1/assets/js/4240e511ac61ecfff2d8.js (added)
-
tags/1.7.1/assets/js/4afb21e314317a97602a.wasm (added)
-
tags/1.7.1/assets/js/6ac3239376efce53600f.wasm (added)
-
tags/1.7.1/assets/js/8206b9ffe333bdb791d8.wasm (added)
-
tags/1.7.1/assets/js/admin.bundle.js (added)
-
tags/1.7.1/assets/js/assets_js_squeeze_js-assets_js_worker_js.script.bundle.js (added)
-
tags/1.7.1/assets/js/assets_js_worker_js.bundle.js (added)
-
tags/1.7.1/assets/js/assets_js_worker_js.script.bundle.js (added)
-
tags/1.7.1/assets/js/c96242667dc8e630897a.wasm (added)
-
tags/1.7.1/assets/js/editor.bundle.js (added)
-
tags/1.7.1/assets/js/node_modules_jsquash_avif_codec_enc_avif_enc_mt_worker_js.bundle.js (added)
-
tags/1.7.1/assets/js/node_modules_jsquash_avif_codec_enc_avif_enc_mt_worker_js.script.bundle.js (added)
-
tags/1.7.1/assets/js/node_modules_jsquash_oxipng_codec_pkg-parallel_index_js.script.bundle.js (added)
-
tags/1.7.1/assets/js/node_modules_jsquash_oxipng_codec_pkg-parallel_snippets_wasm-bindgen-rayon-3d2df09ebec17a22_s-312b0a0.js (added)
-
tags/1.7.1/assets/js/node_modules_jsquash_oxipng_codec_pkg-parallel_snippets_wasm-bindgen-rayon-3d2df09ebec17a22_s-312b0a0.script.bundle.js (added)
-
tags/1.7.1/assets/js/node_modules_jsquash_oxipng_codec_pkg-parallel_snippets_wasm-bindgen-rayon-3d2df09ebec17a22_s-312b0a1.js (added)
-
tags/1.7.1/assets/js/node_modules_jsquash_oxipng_codec_pkg-parallel_snippets_wasm-bindgen-rayon-3d2df09ebec17a22_s-312b0a1.script.bundle.js (added)
-
tags/1.7.1/assets/js/node_modules_jsquash_oxipng_codec_pkg-parallel_snippets_wasm-bindgen-rayon-3d2df09ebec17a22_s-3a2b7f.script.bundle.js (added)
-
tags/1.7.1/assets/js/node_modules_jsquash_oxipng_codec_pkg-parallel_squoosh_oxipng_js.script.bundle.js (added)
-
tags/1.7.1/assets/js/node_modules_jsquash_oxipng_codec_pkg_squoosh_oxipng_js.script.bundle.js (added)
-
tags/1.7.1/assets/js/script.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_avif_codec_enc_avif_enc_js.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_avif_codec_enc_avif_enc_js.script.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_avif_codec_enc_avif_enc_mt_js.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_avif_codec_enc_avif_enc_mt_js.script.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_avif_decode_js-node_modules_jsquash_avif_encode_js-node_modules_-29c710.script.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_avif_decode_js-node_modules_jsquash_avif_encode_js-node_modules_-515c76.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_avif_decode_js-node_modules_jsquash_avif_encode_js-node_modules_-515c76.script.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_avif_decode_js-node_modules_jsquash_avif_encode_js-node_modules_-55a88c.script.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_avif_decode_js-node_modules_jsquash_avif_encode_js-node_modules_-88b2ac.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_jpeg_decode_js-node_modules_jsquash_jpeg_encode_js-node_modules_-72a791.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_jpeg_decode_js-node_modules_jsquash_jpeg_encode_js-node_modules_-9c2145.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_oxipng_codec_pkg-parallel_squoosh_oxipng_js.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_oxipng_codec_pkg-parallel_squoosh_oxipng_js.script.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_oxipng_codec_pkg_squoosh_oxipng_js.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_oxipng_codec_pkg_squoosh_oxipng_js.script.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_webp_codec_enc_webp_enc_js.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_webp_codec_enc_webp_enc_js.script.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_webp_codec_enc_webp_enc_simd_js.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_jsquash_webp_codec_enc_webp_enc_simd_js.script.bundle.js (added)
-
tags/1.7.1/assets/js/vendors-node_modules_piexifjs_piexif_js-node_modules_jsquash_avif_decode_js-node_modules_jsqu-2d8528.bundle.js (added)
-
tags/1.7.1/assets/templates (added)
-
tags/1.7.1/assets/templates/directory-item-empty.html (added)
-
tags/1.7.1/assets/templates/directory-item.html (added)
-
tags/1.7.1/assets/templates/log-details-button.html (added)
-
tags/1.7.1/assets/templates/log-step.html (added)
-
tags/1.7.1/assets/templates/log-wrapper.html (added)
-
tags/1.7.1/assets/templates/path-list-item.html (added)
-
tags/1.7.1/inc (added)
-
tags/1.7.1/inc/handlers.php (added)
-
tags/1.7.1/inc/helpers.php (added)
-
tags/1.7.1/inc/settings.php (added)
-
tags/1.7.1/languages (added)
-
tags/1.7.1/languages/squeeze-uk-0282bc9bb3139ae7f3090194c10090b4.json (added)
-
tags/1.7.1/languages/squeeze-uk-08ec2477f322448a2db534074ac234a4.json (added)
-
tags/1.7.1/languages/squeeze-uk-1c72eced88847462c7e0967d0b7e5a17.json (added)
-
tags/1.7.1/languages/squeeze-uk-24b88c172a2803f6ee0c4956b1a8afb1.json (added)
-
tags/1.7.1/languages/squeeze-uk-6ae642d7d2342ed41f24938c32a6f536.json (added)
-
tags/1.7.1/languages/squeeze-uk-81ea56f743cbb228777c5cd8e3e8aac3.json (added)
-
tags/1.7.1/languages/squeeze-uk-8a8cc8d6792f201000247ed42d4140e8.json (added)
-
tags/1.7.1/languages/squeeze-uk-ac76f1d75eb777f747e6708b19d31b78.json (added)
-
tags/1.7.1/languages/squeeze-uk-b1f8d5e52cbbc262494fff20eec962fd.json (added)
-
tags/1.7.1/languages/squeeze-uk-e28a2098475e8c88fb444548eea784e3.json (added)
-
tags/1.7.1/languages/squeeze-uk-e9cc8298549e98be96351e6176bc85d5.json (added)
-
tags/1.7.1/languages/squeeze-uk-ebfe0cea630778e3fc5018cec6e46aac.json (added)
-
tags/1.7.1/languages/squeeze-uk-f98be19bec7bc848ce3ed383c60037c1.json (added)
-
tags/1.7.1/languages/squeeze-uk-ff025606f2061c02a097c943a471265d.json (added)
-
tags/1.7.1/languages/squeeze-uk.mo (added)
-
tags/1.7.1/languages/squeeze-uk.po (added)
-
tags/1.7.1/languages/squeeze.pot (added)
-
tags/1.7.1/readme.txt (added)
-
tags/1.7.1/squeeze.php (added)
-
trunk/assets/js/admin.bundle.js (modified) (1 diff)
-
trunk/assets/js/editor.bundle.js (modified) (2 diffs)
-
trunk/assets/js/script.bundle.js (modified) (1 diff)
-
trunk/readme.txt (modified) (4 diffs)
-
trunk/squeeze.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
squeeze/trunk/assets/js/admin.bundle.js
r3345284 r3346520 47 47 /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 48 48 49 eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ SQUEEZE)\n/* harmony export */ });\n/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./helpers.js */ \"./assets/js/helpers.js\");\n/* harmony import */ var browser_image_compression__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! browser-image-compression */ \"./node_modules/browser-image-compression/dist/browser-image-compression.mjs\");\n/* harmony import */ var _jsquash_webp__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @jsquash/webp */ \"./node_modules/@jsquash/webp/encode.js\");\n\r\n\r\n\r\n\r\n\r\n\r\nconst { __ } = wp.i18n; // Import __() from wp.i18n\r\n\r\nclass SQUEEZE {\r\n\r\n constructor(squeeze) {\r\n this.options = JSON.parse(squeeze.options); // plugin options\r\n this.nonce = squeeze.nonce; // nonce\r\n this.ajaxUrl = squeeze.ajaxUrl; // ajax url\r\n this.timeout = parseInt(this.options.timeout) * 1000; // convert to milliseconds\r\n this.poolSize = navigator.hardwareConcurrency || 1; // number of threads\r\n }\r\n\r\n handleCompress = async ( attachment, type = 'uncompressed' ) => {\r\n const attachmentData = attachment.attributes;\r\n const url = attachmentData?.originalImageURL ?? attachmentData.url;\r\n const mime = attachmentData.mime;\r\n const name = attachmentData.name;\r\n const filename = attachmentData?.originalImageName ?? attachmentData.filename;\r\n const attachmentID = attachmentData.id;\r\n const sizes = attachmentData.sizes;\r\n const format = mime.split(\"/\")[1];\r\n const sourceType = format;\r\n const outputType = format;\r\n const skipFull = attachmentData?.skipFull ?? attachmentData.originalImageName === undefined ? true : false;\r\n const timeout = this.timeout;\r\n const isPreview = attachmentData?.isPreview ?? false;\r\n const file = attachmentData?.file ?? null; // the original file, it passed for the newly uploaded attachment to compress it\r\n\r\n // the original compressed image base64, it passed when the attachment was already compressed and we need to compress thumbs only\r\n // we pass it in order to prevent re-compressing the original image when we need to compress thumbs only\r\n const base64Compressed = attachmentData?.base64Compressed ?? null; \r\n const base64WebpCompressed = attachmentData?.base64WebpCompressed ?? null; // the original compressed webp image base64\r\n\r\n const worker = new Worker(new URL(/* worker import */ __webpack_require__.p + __webpack_require__.u(\"assets_js_worker_js\"), __webpack_require__.b), {type: undefined}); // worker url\r\n const channel = new MessageChannel();\r\n worker.postMessage({\r\n action: 'compress',\r\n format,\r\n url,\r\n name,\r\n sourceType,\r\n outputType,\r\n mime,\r\n sizes,\r\n skipFull,\r\n timeout,\r\n isPreview,\r\n file,\r\n base64Compressed,\r\n base64WebpCompressed,\r\n type,\r\n options: this.options,\r\n //urlOriginal: urlOriginal,\r\n }, [channel.port2]);\r\n\r\n // Listen for compression requests from the worker via port1\r\n channel.port1.onmessage = async (ev) => {\r\n //console.log(\"Worker received compression request:\", ev.data);\r\n const { id, action, fileOrArrayBuffer, mime, options } = ev.data;\r\n if (action !== 'imageCompression') return;\r\n\r\n try {\r\n // Accept ArrayBuffer or Blob\r\n const blob = (fileOrArrayBuffer instanceof ArrayBuffer)\r\n ? new Blob([this.toUint8Array(fileOrArrayBuffer)], { type: mime })\r\n : fileOrArrayBuffer;\r\n\r\n const compressedBlob = await (0,browser_image_compression__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(blob, { useWebWorker: true, ...(options || {}) });\r\n\r\n // quick checks\r\n //console.log('compressedBlob.type, size:', compressedBlob.type, compressedBlob.size);\r\n // normalize final binary\r\n const compressedArrayBuffer = await compressedBlob.arrayBuffer();\r\n\r\n // Reply with the compressed ArrayBuffer (transfer to avoid copy)\r\n channel.port1.postMessage({ id, ok: true, arrayBuffer: compressedArrayBuffer }, [compressedArrayBuffer]);\r\n } catch (err) {\r\n channel.port1.postMessage({ id, ok: false, error: err.message || String(err) });\r\n }\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const timeoutId = setTimeout(() => {\r\n worker.terminate();\r\n console.warn('Worker compress terminated', name, sourceType, outputType);\r\n reject(__('Request timed out.', 'squeeze'));\r\n }, timeout);\r\n\r\n worker.onmessage = (event) => {\r\n clearTimeout(timeoutId);\r\n if (event.data.error) {\r\n reject(event.data.error);\r\n } else {\r\n resolve(event.data);\r\n }\r\n worker.terminate();\r\n };\r\n\r\n worker.onerror = (error) => {\r\n clearTimeout(timeoutId);\r\n reject(`Worker error: ${error.message}`);\r\n };\r\n });\r\n\r\n }\r\n\r\n handleUpload = async ({ attachment, base64, type = 'uncompressed', mediaIDs = [] }) => {\r\n\r\n const attachmentData = attachment.attributes;\r\n const url = attachmentData?.originalImageURL ?? attachmentData.url;\r\n const mime = attachmentData.mime;\r\n const filename = attachmentData?.originalImageName ?? attachmentData.filename;\r\n const attachmentID = attachmentData.id;\r\n const format = base64?.isDirectWebp && type !== 'path' ? 'webp' : mime.split(\"/\")[1];\r\n const sizes = attachmentData.sizes;\r\n\r\n const isDirectWebp = base64?.isDirectWebp && mime !== 'image/webp';\r\n const isBackupOriginal = this.options?.backup_original ?? false; // check if backup original is enabled\r\n let originalFile = attachmentData?.originalFile ?? null; // the original file, used for creating backup\r\n \r\n // if originalFile is not provided, try to get it from the URL\r\n // used when the attachment needs to be converted to webp from jpg or png\r\n if (!originalFile && type !== 'path' && isBackupOriginal && isDirectWebp) {\r\n const file = await (0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.getFileFromUrl)(url, filename, mime.split(\"/\")[1])\r\n //console.log('conveting original file to webp', file);\r\n originalFile = await this.convertFileToWebp(file);\r\n }\r\n //console.log('handleUpload attachmentData', attachmentData)\r\n\r\n const data = {\r\n action: 'squeeze_update_attachment',\r\n _ajax_nonce: this.nonce,\r\n filename: filename,\r\n type: 'image',\r\n format: format,\r\n base64: base64.base64,\r\n base64Sizes: base64.base64Sizes,\r\n base64Webp: base64.base64Webp,\r\n base64SizesWebp: base64.base64SizesWebp,\r\n attachmentID: attachmentID,\r\n url: url,\r\n process: type,\r\n originalFile: originalFile,\r\n }\r\n\r\n const formData = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.objectToFormData)(data);\r\n\r\n //console.log('squeeze_update_attachment', JSON.stringify(data, null, 2));\r\n\r\n try {\r\n const uploadResponse = await jQuery.ajax({\r\n url: this.ajaxUrl, // + '111',\r\n type: 'POST',\r\n data: formData,\r\n processData: false, // very important!\r\n contentType: false, // very important!\r\n });\r\n\r\n //if (uploadResponse.success) {\r\n uploadResponse['mediaIDs'] = mediaIDs;\r\n //}\r\n\r\n if (!uploadResponse?.data?.url) {\r\n uploadResponse.url = url; // fallback to original URL if not provided\r\n }\r\n\r\n //console.log('uploadResponse', JSON.stringify(uploadResponse, null, 2));\r\n\r\n return uploadResponse;\r\n \r\n } catch (error) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': error.message,\r\n 'success': false\r\n };\r\n }\r\n\r\n }\r\n\r\n handleBulkUpload = async (type = 'uncompressed', mediaIDs = []) => {\r\n let currentID;\r\n let attachment;\r\n\r\n switch (type) {\r\n case 'all':\r\n case 'uncompressed':\r\n currentID = mediaIDs[0];\r\n break;\r\n case 'path':\r\n currentID = mediaIDs[0]?.filename;\r\n break;\r\n default:\r\n currentID = 0;\r\n break;\r\n }\r\n\r\n if (type === 'path') {\r\n\r\n \r\n\r\n attachment = {\r\n attributes: {\r\n url: mediaIDs[0].url,\r\n mime: mediaIDs[0].mime,\r\n name: mediaIDs[0].name,\r\n filename: mediaIDs[0].filename,\r\n id: mediaIDs[0].id,\r\n sizes: mediaIDs[0]?.sizes,\r\n }\r\n }\r\n\r\n } else {\r\n\r\n const attachmentResponse = await this.getAttachment(currentID); \r\n if (attachmentResponse.success === false) {\r\n if (Array.isArray(mediaIDs)) mediaIDs.shift();\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': attachmentResponse.data\r\n }\r\n }\r\n const attachmentData = attachmentResponse.data;\r\n attachment = {\r\n attributes: {\r\n url: attachmentData.url,\r\n mime: attachmentData.mime,\r\n name: attachmentData.name,\r\n filename: attachmentData.filename,\r\n id: attachmentData.id,\r\n sizes: attachmentData.sizes,\r\n }\r\n }\r\n\r\n }\r\n\r\n if (Array.isArray(mediaIDs)) mediaIDs.shift();\r\n\r\n const mediaType = attachment.attributes.mime.split(\"/\")[0];\r\n const mediaSubType = attachment.attributes.mime.split(\"/\")[1];\r\n\r\n if (!(0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.maybeCompressAttachment)(mediaType, mediaSubType)) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': __('Skipped', 'squeeze')\r\n }\r\n }\r\n\r\n try {\r\n const compressData = await this.handleCompress( attachment, type );\r\n const uploadData = await this.handleUpload({ attachment: attachment, base64: compressData, type: type, mediaIDs: mediaIDs })\r\n\r\n return uploadData;\r\n\r\n } catch (error) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': error?.message ?? error,\r\n 'success': false\r\n }\r\n }\r\n\r\n }\r\n\r\n \r\n\r\n handleRestore = async (attachmentID) => {\r\n const data = {\r\n action: 'squeeze_restore_attachment',\r\n _ajax_nonce: this.nonce,\r\n attachmentID: attachmentID,\r\n };\r\n\r\n const response = await jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n };\r\n\r\n // Get list of attachments by path\r\n getAttachmentsByPath = async (path) => {\r\n\r\n const data = {\r\n action: 'squeeze_get_attachment_by_path',\r\n path: path,\r\n _ajax_nonce: this.nonce,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getAttachment = async (attachmentID) => {\r\n const data = {\r\n action: 'squeeze_get_attachment',\r\n _ajax_nonce: this.nonce,\r\n attachmentID: attachmentID,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getNextAttachments = async (page = 1, type = 'uncompressed', lastId = 0) => {\r\n const data = {\r\n action: 'squeeze_get_next_attachments',\r\n _ajax_nonce: this.nonce,\r\n page: page,\r\n type: type,\r\n lastId: lastId,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getDirectories = async (parentDir = false) => {\r\n const data = {\r\n action: 'squeeze_get_directories',\r\n _ajax_nonce: this.nonce,\r\n }\r\n\r\n if (parentDir) {\r\n data['parentDir'] = parentDir;\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n \r\n\r\n setOptions = async (options) => {\r\n const data = {\r\n action: 'squeeze_set_options',\r\n _ajax_nonce: this.nonce,\r\n options: options,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n /**\r\n * Compresses an image before upload.\r\n * If the compressSizes parameter is provided, it will compress the sizes specified along with the original image.\r\n * \r\n * @param {File} file - The file to be compressed\r\n * @param {object} compressSizes - Optional sizes for compression\r\n * @returns {Promise<object|boolean>} - Returns a Promise that resolves to the compressed file as a base64 object or false if the file is invalid\r\n */\r\n compressBeforeUpload = async (file, compressSizes = null) => {\r\n if (!(file instanceof Blob) && !(file instanceof File)) {\r\n console.error('Invalid file type:', file);\r\n return false; // Return false if the file is not a Blob or File\r\n }\r\n\r\n const attachment = {\r\n attributes: {\r\n url: null,\r\n mime: file.type,\r\n name: file.name,\r\n filename: file.name,\r\n id: 0,\r\n sizes: compressSizes,\r\n file: file,\r\n skipFull: false,\r\n }\r\n }\r\n\r\n try {\r\n const base64Obj = await this.handleCompress(attachment);\r\n\r\n return base64Obj; // Return the compressed file as base64Obj\r\n } catch (error) {\r\n console.error(error);\r\n return error; // Return the error for further handling\r\n }\r\n }\r\n\r\n isWebpEncodingSupported = async () => {\r\n try {\r\n const canvas = document.createElement('canvas');\r\n if (!canvas.toDataURL) return false;\r\n const data = canvas.toDataURL('image/webp');\r\n // WebP signature starts with \"data:image/webp\"\r\n return data.indexOf('data:image/webp') === 0;\r\n } catch (e) {\r\n return false;\r\n }\r\n }\r\n\r\n handleConvertFileToWebp = async (args) => {\r\n const timeout = this.timeout;\r\n \r\n const { file, sourceType, name } = args;\r\n const fileBuffer = await file.arrayBuffer();\r\n const mime = sourceType === 'png' ? 'image/png' : 'image/jpeg';\r\n const inputBlob = new Blob([fileBuffer], { type: mime });\r\n // compression options you can tune\r\n const imageCompressionOptions = {\r\n useWebWorker: true,\r\n fileType: 'image/webp',\r\n };\r\n\r\n return new Promise(async (resolve, reject) => {\r\n const timeoutId = setTimeout(() => {\r\n console.warn('Worker convertToWebp terminated', name);\r\n reject(new Error(__('Request timed out.', 'squeeze')));\r\n }, timeout);\r\n\r\n try {\r\n\r\n const compressedBlob = await (0,browser_image_compression__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(inputBlob, imageCompressionOptions);\r\n\r\n // quick checks\r\n //console.log('compressedBlob.type, size:', compressedBlob.type, compressedBlob.size);\r\n // normalize final binary\r\n const webpArrayBuffer = await compressedBlob.arrayBuffer();\r\n\r\n const webpName = name.replace(/\\.\\w+$/, '.webp');\r\n const webpFile = new File([webpArrayBuffer], webpName, { type: 'image/webp', lastModified: Date.now() });\r\n\r\n clearTimeout(timeoutId);\r\n resolve({ webpFile }); // must return object with webpFile prop\r\n } catch (error) {\r\n clearTimeout(timeoutId);\r\n console.error('Error during image processing:', error);\r\n reject(error);\r\n }\r\n });\r\n \r\n }\r\n\r\n // helper: normalize buffer-like inputs to Uint8Array\r\n toUint8Array = (bufferLike) => {\r\n if (bufferLike instanceof Uint8Array) return bufferLike;\r\n if (bufferLike instanceof ArrayBuffer) return new Uint8Array(bufferLike);\r\n if (ArrayBuffer.isView(bufferLike)) return new Uint8Array(bufferLike.buffer, bufferLike.byteOffset, bufferLike.byteLength);\r\n // Node Buffer (in some environments) has .buffer and .byteOffset\r\n if (bufferLike && typeof bufferLike === 'object' && bufferLike.buffer) {\r\n return new Uint8Array(bufferLike.buffer, bufferLike.byteOffset || 0, bufferLike.byteLength || bufferLike.length);\r\n }\r\n throw new Error('Unsupported buffer type: ' + Object.prototype.toString.call(bufferLike));\r\n }\r\n\r\n /**\r\n * Convert an image File to WebP.\r\n *\r\n * @param {File} inputFile — the original image File (e.g. JPEG, PNG).\r\n * @returns {Promise<File>} — a Promise that resolves to a WebP File.\r\n */\r\n convertFileToWebp = async (inputFile) => {\r\n // Ensure it’s an image\r\n if (!inputFile.type.startsWith('image/')) {\r\n throw new Error('Input must be an image File');\r\n }\r\n const args = {\r\n file: inputFile,\r\n sourceType: inputFile.type.split('/')[1], // e.g. 'jpeg', 'png'\r\n name: inputFile.name,\r\n }\r\n const webpSupported = await this.isWebpEncodingSupported();\r\n try {\r\n // Convert the image to WebP\r\n let webpData;\r\n if (webpSupported) {\r\n webpData = await this.handleConvertFileToWebp(args);\r\n } else {\r\n console.warn('WebP encoding not supported on this device. Using original format instead.');\r\n const webpName = args.name.replace(/\\.\\w+$/, '.webp');\r\n webpData = await this.convertToWebpWithJsquash(inputFile, webpName);\r\n }\r\n if (!webpData || !webpData.webpFile) {\r\n throw new Error('Failed to convert image to WebP');\r\n }\r\n\r\n return webpData.webpFile; // Return the WebP File\r\n\r\n } catch (error) {\r\n console.error('Error converting image to WebP:', error);\r\n throw error; // Re-throw the error for further handling\r\n }\r\n\r\n }\r\n\r\n /**\r\n * Convert an input (File | Blob | ArrayBuffer | Uint8Array) to a WebP File using @jsquash/webp.\r\n * @param {File|Blob|ArrayBuffer|Uint8Array} input\r\n * @param {string} outputName - desired filename for the .webp output\r\n * @param {{quality?: number, lossless?: boolean, method?: number}} [opts] - encoder options (quality 0-100)\r\n * @returns {Promise<File>} - WebP File\r\n */\r\n convertToWebpWithJsquash = async (input, outputName = 'out.webp', opts = { quality: 100 }) => {\r\n // Normalize input to Blob\r\n let blob;\r\n if (input instanceof Blob) {\r\n blob = input;\r\n } else if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {\r\n blob = new Blob([input]);\r\n } else {\r\n throw new Error('Unsupported input type. Provide File, Blob, ArrayBuffer or TypedArray.');\r\n }\r\n\r\n // Load image into <img>\r\n const dataURL = await new Promise((resolve, reject) => {\r\n const fr = new FileReader();\r\n fr.onload = () => resolve(fr.result);\r\n fr.onerror = reject;\r\n fr.readAsDataURL(blob);\r\n });\r\n\r\n const img = await new Promise((resolve, reject) => {\r\n const i = new Image();\r\n i.onload = () => resolve(i);\r\n i.onerror = () => reject(new Error('Failed to load image for conversion'));\r\n i.src = dataURL;\r\n // avoid cross-origin taint if data-url is used\r\n });\r\n\r\n // Draw to canvas and extract ImageData\r\n const canvas = document.createElement('canvas');\r\n canvas.width = img.naturalWidth || img.width;\r\n canvas.height = img.naturalHeight || img.height;\r\n const ctx = canvas.getContext('2d');\r\n ctx.drawImage(img, 0, 0);\r\n const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\r\n\r\n // encode -> returns ArrayBuffer (WASM libwebp)\r\n // opts example: { quality: 75, lossless: false, method: 4 }\r\n const webpArrayBuffer = await (0,_jsquash_webp__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(imageData, opts);\r\n\r\n // Validate a little (size)\r\n if (!webpArrayBuffer || webpArrayBuffer.byteLength < 20) {\r\n throw new Error('jsquash produced an invalid WebP buffer');\r\n }\r\n\r\n // Wrap into File\r\n const webpFile = new File([webpArrayBuffer], outputName.replace(/\\.\\w+$/, '.webp'), {\r\n type: 'image/webp',\r\n lastModified: Date.now(),\r\n });\r\n\r\n return {webpFile};\r\n }\r\n\r\n}\n\n//# sourceURL=webpack:///./assets/js/squeeze.js?");49 eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ SQUEEZE)\n/* harmony export */ });\n/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./helpers.js */ \"./assets/js/helpers.js\");\n/* harmony import */ var browser_image_compression__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! browser-image-compression */ \"./node_modules/browser-image-compression/dist/browser-image-compression.mjs\");\n/* harmony import */ var _jsquash_webp__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @jsquash/webp */ \"./node_modules/@jsquash/webp/encode.js\");\n\r\n\r\n\r\n\r\n\r\n\r\nconst { __ } = wp.i18n; // Import __() from wp.i18n\r\n\r\nclass SQUEEZE {\r\n\r\n constructor(squeeze) {\r\n this.options = JSON.parse(squeeze.options); // plugin options\r\n this.nonce = squeeze.nonce; // nonce\r\n this.ajaxUrl = squeeze.ajaxUrl; // ajax url\r\n this.timeout = parseInt(this.options.timeout) * 1000; // convert to milliseconds\r\n this.poolSize = navigator.hardwareConcurrency || 1; // number of threads\r\n }\r\n\r\n handleCompress = async ( attachment, type = 'uncompressed' ) => {\r\n const attachmentData = attachment.attributes;\r\n const url = attachmentData?.originalImageURL ?? attachmentData.url;\r\n const mime = attachmentData.mime;\r\n const name = attachmentData.name;\r\n const filename = attachmentData?.originalImageName ?? attachmentData.filename;\r\n const attachmentID = attachmentData.id;\r\n const sizes = attachmentData.sizes;\r\n const format = mime.split(\"/\")[1];\r\n const sourceType = format;\r\n const outputType = format;\r\n const skipFull = attachmentData?.skipFull ?? attachmentData.originalImageName === undefined ? true : false;\r\n const timeout = this.timeout;\r\n const isPreview = attachmentData?.isPreview ?? false;\r\n const file = attachmentData?.file ?? null; // the original file, it passed for the newly uploaded attachment to compress it\r\n\r\n // the original compressed image base64, it passed when the attachment was already compressed and we need to compress thumbs only\r\n // we pass it in order to prevent re-compressing the original image when we need to compress thumbs only\r\n const base64Compressed = attachmentData?.base64Compressed ?? null; \r\n const base64WebpCompressed = attachmentData?.base64WebpCompressed ?? null; // the original compressed webp image base64\r\n\r\n const worker = new Worker(new URL(/* worker import */ __webpack_require__.p + __webpack_require__.u(\"assets_js_worker_js\"), __webpack_require__.b), {type: undefined}); // worker url\r\n const channel = new MessageChannel();\r\n worker.postMessage({\r\n action: 'compress',\r\n format,\r\n url,\r\n name,\r\n sourceType,\r\n outputType,\r\n mime,\r\n sizes,\r\n skipFull,\r\n timeout,\r\n isPreview,\r\n file,\r\n base64Compressed,\r\n base64WebpCompressed,\r\n type,\r\n options: this.options,\r\n //urlOriginal: urlOriginal,\r\n }, [channel.port2]);\r\n\r\n // Listen for compression requests from the worker via port1\r\n channel.port1.onmessage = async (ev) => {\r\n //console.log(\"Worker received compression request:\", ev.data);\r\n const { id, action, fileOrArrayBuffer, mime, options } = ev.data;\r\n if (action !== 'imageCompression') return;\r\n\r\n try {\r\n // Accept ArrayBuffer or Blob\r\n const blob = (fileOrArrayBuffer instanceof ArrayBuffer)\r\n ? new Blob([this.toUint8Array(fileOrArrayBuffer)], { type: mime })\r\n : fileOrArrayBuffer;\r\n\r\n const compressedBlob = await (0,browser_image_compression__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(blob, { useWebWorker: true, ...(options || {}) });\r\n\r\n // quick checks\r\n //console.log('compressedBlob.type, size:', compressedBlob.type, compressedBlob.size);\r\n // normalize final binary\r\n const compressedArrayBuffer = await compressedBlob.arrayBuffer();\r\n\r\n // Reply with the compressed ArrayBuffer (transfer to avoid copy)\r\n channel.port1.postMessage({ id, ok: true, arrayBuffer: compressedArrayBuffer }, [compressedArrayBuffer]);\r\n } catch (err) {\r\n channel.port1.postMessage({ id, ok: false, error: err.message || String(err) });\r\n }\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const timeoutId = setTimeout(() => {\r\n worker.terminate();\r\n console.warn('Worker compress terminated', name, sourceType, outputType);\r\n reject(__('Request timed out.', 'squeeze'));\r\n }, timeout);\r\n\r\n worker.onmessage = (event) => {\r\n clearTimeout(timeoutId);\r\n if (event.data.error) {\r\n reject(event.data.error);\r\n } else {\r\n resolve(event.data);\r\n }\r\n worker.terminate();\r\n };\r\n\r\n worker.onerror = (error) => {\r\n clearTimeout(timeoutId);\r\n reject(`Worker error: ${error.message}`);\r\n };\r\n });\r\n\r\n }\r\n\r\n handleUpload = async ({ attachment, base64, type = 'uncompressed', mediaIDs = [] }) => {\r\n\r\n const attachmentData = attachment.attributes;\r\n const url = attachmentData?.originalImageURL ?? attachmentData.url;\r\n const mime = attachmentData.mime;\r\n const filename = attachmentData?.originalImageName ?? attachmentData.filename;\r\n const attachmentID = attachmentData.id;\r\n const format = base64?.isDirectWebp && type !== 'path' ? 'webp' : mime.split(\"/\")[1];\r\n const sizes = attachmentData.sizes;\r\n\r\n const isDirectWebp = base64?.isDirectWebp && mime !== 'image/webp';\r\n const isBackupOriginal = this.options?.backup_original ?? false; // check if backup original is enabled\r\n let originalFile = attachmentData?.originalFile ?? null; // the original file, used for creating backup\r\n \r\n // if originalFile is not provided, try to get it from the URL\r\n // used when the attachment needs to be converted to webp from jpg or png\r\n if (!originalFile && type !== 'path' && isBackupOriginal && isDirectWebp) {\r\n const file = await (0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.getFileFromUrl)(url, filename, mime.split(\"/\")[1])\r\n //console.log('conveting original file to webp', file);\r\n originalFile = await this.convertFileToWebp(file);\r\n }\r\n //console.log('handleUpload attachmentData', attachmentData)\r\n\r\n const data = {\r\n action: 'squeeze_update_attachment',\r\n _ajax_nonce: this.nonce,\r\n filename: filename,\r\n type: 'image',\r\n format: format,\r\n base64: base64.base64,\r\n base64Sizes: base64.base64Sizes,\r\n base64Webp: base64.base64Webp,\r\n base64SizesWebp: base64.base64SizesWebp,\r\n attachmentID: attachmentID,\r\n url: url,\r\n process: type,\r\n originalFile: originalFile,\r\n }\r\n\r\n const formData = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.objectToFormData)(data);\r\n\r\n //console.log('squeeze_update_attachment', JSON.stringify(data, null, 2));\r\n\r\n try {\r\n const uploadResponse = await jQuery.ajax({\r\n url: this.ajaxUrl, // + '111',\r\n type: 'POST',\r\n data: formData,\r\n processData: false, // very important!\r\n contentType: false, // very important!\r\n });\r\n\r\n //if (uploadResponse.success) {\r\n uploadResponse['mediaIDs'] = mediaIDs;\r\n //}\r\n\r\n if (!uploadResponse?.data?.url) {\r\n uploadResponse.url = url; // fallback to original URL if not provided\r\n }\r\n\r\n //console.log('uploadResponse', JSON.stringify(uploadResponse, null, 2));\r\n\r\n return uploadResponse;\r\n \r\n } catch (error) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': error.message,\r\n 'success': false\r\n };\r\n }\r\n\r\n }\r\n\r\n handleBulkUpload = async (type = 'uncompressed', mediaIDs = []) => {\r\n let currentID;\r\n let attachment;\r\n\r\n switch (type) {\r\n case 'all':\r\n case 'uncompressed':\r\n currentID = mediaIDs[0];\r\n break;\r\n case 'path':\r\n currentID = mediaIDs[0]?.filename;\r\n break;\r\n default:\r\n currentID = 0;\r\n break;\r\n }\r\n\r\n if (type === 'path') {\r\n\r\n \r\n\r\n attachment = {\r\n attributes: {\r\n url: mediaIDs[0].url,\r\n mime: mediaIDs[0].mime,\r\n name: mediaIDs[0].name,\r\n filename: mediaIDs[0].filename,\r\n id: mediaIDs[0].id,\r\n sizes: mediaIDs[0]?.sizes,\r\n }\r\n }\r\n\r\n } else {\r\n\r\n const attachmentResponse = await this.getAttachment(currentID); \r\n if (attachmentResponse.success === false) {\r\n if (Array.isArray(mediaIDs)) mediaIDs.shift();\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': attachmentResponse.data\r\n }\r\n }\r\n const attachmentData = attachmentResponse.data;\r\n attachment = {\r\n attributes: {\r\n url: attachmentData.url,\r\n mime: attachmentData.mime,\r\n name: attachmentData.name,\r\n filename: attachmentData.filename,\r\n id: attachmentData.id,\r\n sizes: attachmentData.sizes,\r\n }\r\n }\r\n\r\n }\r\n\r\n if (Array.isArray(mediaIDs)) mediaIDs.shift();\r\n\r\n const mediaType = attachment.attributes.mime.split(\"/\")[0];\r\n const mediaSubType = attachment.attributes.mime.split(\"/\")[1];\r\n\r\n if (!(0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.maybeCompressAttachment)(mediaType, mediaSubType)) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': __('Skipped', 'squeeze')\r\n }\r\n }\r\n\r\n try {\r\n const compressData = await this.handleCompress( attachment, type );\r\n const uploadData = await this.handleUpload({ attachment: attachment, base64: compressData, type: type, mediaIDs: mediaIDs })\r\n\r\n return uploadData;\r\n\r\n } catch (error) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': error?.message ?? error,\r\n 'success': false\r\n }\r\n }\r\n\r\n }\r\n\r\n \r\n\r\n handleRestore = async (attachmentID) => {\r\n const data = {\r\n action: 'squeeze_restore_attachment',\r\n _ajax_nonce: this.nonce,\r\n attachmentID: attachmentID,\r\n };\r\n\r\n const response = await jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n };\r\n\r\n // Get list of attachments by path\r\n getAttachmentsByPath = async (path) => {\r\n\r\n const data = {\r\n action: 'squeeze_get_attachment_by_path',\r\n path: path,\r\n _ajax_nonce: this.nonce,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getAttachment = async (attachmentID) => {\r\n const data = {\r\n action: 'squeeze_get_attachment',\r\n _ajax_nonce: this.nonce,\r\n attachmentID: attachmentID,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getNextAttachments = async (page = 1, type = 'uncompressed', lastId = 0) => {\r\n const data = {\r\n action: 'squeeze_get_next_attachments',\r\n _ajax_nonce: this.nonce,\r\n page: page,\r\n type: type,\r\n lastId: lastId,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getDirectories = async (parentDir = false) => {\r\n const data = {\r\n action: 'squeeze_get_directories',\r\n _ajax_nonce: this.nonce,\r\n }\r\n\r\n if (parentDir) {\r\n data['parentDir'] = parentDir;\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n \r\n\r\n setOptions = async (options) => {\r\n const data = {\r\n action: 'squeeze_set_options',\r\n _ajax_nonce: this.nonce,\r\n options: options,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n /**\r\n * Compresses an image before upload.\r\n * If the compressSizes parameter is provided, it will compress the sizes specified along with the original image.\r\n * \r\n * @param {File} file - The file to be compressed\r\n * @param {object} compressSizes - Optional sizes for compression\r\n * @returns {Promise<object|boolean>} - Returns a Promise that resolves to the compressed file as a base64 object or false if the file is invalid\r\n */\r\n compressBeforeUpload = async (file, compressSizes = null) => {\r\n if (!(file instanceof Blob) && !(file instanceof File)) {\r\n console.error('Invalid file type:', file);\r\n return false; // Return false if the file is not a Blob or File\r\n }\r\n\r\n const attachment = {\r\n attributes: {\r\n url: null,\r\n mime: file.type,\r\n name: file.name,\r\n filename: file.name,\r\n id: 0,\r\n sizes: compressSizes,\r\n file: file,\r\n skipFull: false,\r\n }\r\n }\r\n\r\n try {\r\n const base64Obj = await this.handleCompress(attachment);\r\n\r\n return base64Obj; // Return the compressed file as base64Obj\r\n } catch (error) {\r\n console.error(error);\r\n return error; // Return the error for further handling\r\n }\r\n }\r\n\r\n isWebpEncodingSupported = async () => {\r\n try {\r\n const canvas = document.createElement('canvas');\r\n if (!canvas.toDataURL) return false;\r\n const data = canvas.toDataURL('image/webp');\r\n // WebP signature starts with \"data:image/webp\"\r\n return data.indexOf('data:image/webp') === 0;\r\n } catch (e) {\r\n return false;\r\n }\r\n }\r\n\r\n handleConvertFileToWebp = async (args) => {\r\n const timeout = this.timeout;\r\n \r\n const { file, sourceType, name } = args;\r\n const fileBuffer = await file.arrayBuffer();\r\n const mime = sourceType === 'png' ? 'image/png' : 'image/jpeg';\r\n const inputBlob = new Blob([fileBuffer], { type: mime });\r\n // compression options you can tune\r\n const imageCompressionOptions = {\r\n useWebWorker: true,\r\n fileType: 'image/webp',\r\n };\r\n\r\n return new Promise(async (resolve, reject) => {\r\n const timeoutId = setTimeout(() => {\r\n console.warn('Worker convertToWebp terminated', name);\r\n reject(new Error(__('Request timed out.', 'squeeze')));\r\n }, timeout);\r\n\r\n try {\r\n\r\n const compressedBlob = await (0,browser_image_compression__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(inputBlob, imageCompressionOptions);\r\n\r\n // quick checks\r\n //console.log('compressedBlob.type, size:', compressedBlob.type, compressedBlob.size);\r\n // normalize final binary\r\n const webpArrayBuffer = await compressedBlob.arrayBuffer();\r\n\r\n const webpName = name.replace(/\\.\\w+$/, '.webp');\r\n const webpFile = new File([webpArrayBuffer], webpName, { type: 'image/webp', lastModified: Date.now() });\r\n\r\n clearTimeout(timeoutId);\r\n resolve({ webpFile }); // must return object with webpFile prop\r\n } catch (error) {\r\n clearTimeout(timeoutId);\r\n console.error('Error during image processing:', error);\r\n reject(error);\r\n }\r\n });\r\n \r\n }\r\n\r\n // helper: normalize buffer-like inputs to Uint8Array\r\n toUint8Array = (bufferLike) => {\r\n if (bufferLike instanceof Uint8Array) return bufferLike;\r\n if (bufferLike instanceof ArrayBuffer) return new Uint8Array(bufferLike);\r\n if (ArrayBuffer.isView(bufferLike)) return new Uint8Array(bufferLike.buffer, bufferLike.byteOffset, bufferLike.byteLength);\r\n // Node Buffer (in some environments) has .buffer and .byteOffset\r\n if (bufferLike && typeof bufferLike === 'object' && bufferLike.buffer) {\r\n return new Uint8Array(bufferLike.buffer, bufferLike.byteOffset || 0, bufferLike.byteLength || bufferLike.length);\r\n }\r\n throw new Error('Unsupported buffer type: ' + Object.prototype.toString.call(bufferLike));\r\n }\r\n\r\n /**\r\n * Convert an image File to WebP.\r\n *\r\n * @param {File} inputFile — the original image File (e.g. JPEG, PNG).\r\n * @returns {Promise<File>} — a Promise that resolves to a WebP File.\r\n */\r\n convertFileToWebp = async (inputFile) => {\r\n // Ensure it’s an image\r\n if (!inputFile.type.startsWith('image/')) {\r\n throw new Error('Input must be an image File');\r\n }\r\n if (this.isCanvasExtractionBlocked()) {\r\n console.warn('Canvas extraction is blocked');\r\n // Handle the case where canvas extraction is blocked\r\n alert(__('Image conversion blocked by browser privacy setting', 'squeeze'));\r\n throw new Error('Canvas extraction is blocked');\r\n } else {\r\n console.log('Canvas extraction is allowed');\r\n }\r\n const args = {\r\n file: inputFile,\r\n sourceType: inputFile.type.split('/')[1], // e.g. 'jpeg', 'png'\r\n name: inputFile.name,\r\n }\r\n const webpSupported = await this.isWebpEncodingSupported();\r\n try {\r\n // Convert the image to WebP\r\n let webpData;\r\n if (webpSupported) {\r\n //console.log('WebP decoding is supported');\r\n webpData = await this.handleConvertFileToWebp(args);\r\n } else {\r\n console.warn('WebP encoding not supported on this device. Using original format instead.');\r\n const webpName = args.name.replace(/\\.\\w+$/, '.webp');\r\n webpData = await this.convertToWebpWithJsquash(inputFile, webpName);\r\n }\r\n if (!webpData || !webpData.webpFile) {\r\n throw new Error('Failed to convert image to WebP');\r\n }\r\n\r\n return webpData.webpFile; // Return the WebP File\r\n\r\n } catch (error) {\r\n console.error('Error converting image to WebP:', error);\r\n throw error; // Re-throw the error for further handling\r\n }\r\n\r\n }\r\n\r\n /**\r\n * Convert an input (File | Blob | ArrayBuffer | Uint8Array) to a WebP File using @jsquash/webp.\r\n * @param {File|Blob|ArrayBuffer|Uint8Array} input\r\n * @param {string} outputName - desired filename for the .webp output\r\n * @param {{quality?: number, lossless?: boolean, method?: number}} [opts] - encoder options (quality 0-100)\r\n * @returns {Promise<File>} - WebP File\r\n */\r\n convertToWebpWithJsquash = async (input, outputName = 'out.webp', opts = { quality: 100 }) => {\r\n // Normalize input to Blob\r\n let blob;\r\n if (input instanceof Blob) {\r\n blob = input;\r\n } else if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {\r\n blob = new Blob([input]);\r\n } else {\r\n throw new Error('Unsupported input type. Provide File, Blob, ArrayBuffer or TypedArray.');\r\n }\r\n\r\n // Load image into <img>\r\n const dataURL = await new Promise((resolve, reject) => {\r\n const fr = new FileReader();\r\n fr.onload = () => resolve(fr.result);\r\n fr.onerror = reject;\r\n fr.readAsDataURL(blob);\r\n });\r\n\r\n const img = await new Promise((resolve, reject) => {\r\n const i = new Image();\r\n i.onload = () => resolve(i);\r\n i.onerror = () => reject(new Error('Failed to load image for conversion'));\r\n i.src = dataURL;\r\n // avoid cross-origin taint if data-url is used\r\n });\r\n\r\n // Draw to canvas and extract ImageData\r\n const canvas = document.createElement('canvas');\r\n canvas.width = img.naturalWidth || img.width;\r\n canvas.height = img.naturalHeight || img.height;\r\n const ctx = canvas.getContext('2d');\r\n ctx.drawImage(img, 0, 0);\r\n const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\r\n\r\n // encode -> returns ArrayBuffer (WASM libwebp)\r\n // opts example: { quality: 75, lossless: false, method: 4 }\r\n const webpArrayBuffer = await (0,_jsquash_webp__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(imageData, opts);\r\n\r\n // Validate a little (size)\r\n if (!webpArrayBuffer || webpArrayBuffer.byteLength < 20) {\r\n throw new Error('jsquash produced an invalid WebP buffer');\r\n }\r\n\r\n // Wrap into File\r\n const webpFile = new File([webpArrayBuffer], outputName.replace(/\\.\\w+$/, '.webp'), {\r\n type: 'image/webp',\r\n lastModified: Date.now(),\r\n });\r\n\r\n return {webpFile};\r\n }\r\n\r\n isCanvasExtractionBlocked = () => {\r\n try {\r\n const c = document.createElement('canvas');\r\n c.width = 2;\r\n c.height = 2;\r\n const ctx = c.getContext('2d');\r\n if (!ctx) return false; // no ctx => treat as not-blocked here (or handle separately)\r\n\r\n // draw four known opaque pixels (no alpha surprise)\r\n ctx.fillStyle = 'rgb(255,0,0)'; // top-left\r\n ctx.fillRect(0, 0, 1, 1);\r\n\r\n ctx.fillStyle = 'rgb(0,255,0)'; // top-right\r\n ctx.fillRect(1, 0, 1, 1);\r\n\r\n ctx.fillStyle = 'rgb(0,0,255)'; // bottom-left\r\n ctx.fillRect(0, 1, 1, 1);\r\n\r\n ctx.fillStyle = 'rgb(1,2,3)'; // bottom-right (small values detect naive normalization)\r\n ctx.fillRect(1, 1, 1, 1);\r\n\r\n let data;\r\n try {\r\n data = ctx.getImageData(0, 0, 2, 2).data; // Uint8ClampedArray length 16 (4*2*2)\r\n } catch (err) {\r\n // thrown security/blocked error (Firefox/resistFingerprinting sometimes throws)\r\n if (err && (err.name === 'SecurityError' || /blocked/i.test(err.message))) return true;\r\n // other errors: be conservative and return false\r\n return false;\r\n }\r\n\r\n // Expected RGBA order for pixels (top-left, top-right, bottom-left, bottom-right)\r\n const expected = new Uint8ClampedArray([\r\n 255, 0, 0, 255, // top-left\r\n 0, 255, 0, 255, // top-right\r\n 0, 0, 255, 255, // bottom-left\r\n 1, 2, 3, 255 // bottom-right\r\n ]);\r\n\r\n // Compare each channel exactly. If ANY mismatch — canvas data was altered.\r\n for (let i = 0; i < expected.length; i++) {\r\n if (data[i] !== expected[i]) {\r\n // mismatch -> either fingerprint-protection normalized the canvas OR something else tainted it\r\n return true;\r\n }\r\n }\r\n\r\n // No thrown error and pixels match exactly => extraction works\r\n return false;\r\n } catch (e) {\r\n // If something unexpected goes wrong, assume not blocked so caller can handle fallback.\r\n return false;\r\n }\r\n }\r\n\r\n\r\n}\n\n//# sourceURL=webpack:///./assets/js/squeeze.js?"); 50 50 51 51 /***/ }), -
squeeze/trunk/assets/js/editor.bundle.js
r3345284 r3346520 17 17 /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 18 18 19 eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _squeeze_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./squeeze.js */ \"./assets/js/squeeze.js\");\n/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./helpers.js */ \"./assets/js/helpers.js\");\n/* harmony import */ var _handlers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./handlers.js */ \"./assets/js/handlers.js\");\n\r\n\r\n\r\n\r\n\"use strict\";\r\n\r\nconst { sprintf, __ } = wp.i18n; // Import __() from wp.i18n\r\n\r\nconst Squeeze = new _squeeze_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"](squeezeOptions);\r\nconst compressOptions = JSON.parse(squeezeOptions.options);\r\n\r\nconst addFilter = wp.hooks.addFilter;\r\nconst createHigherOrderComponent = wp.compose.createHigherOrderComponent;\r\nconst { createElement: el, Fragment } = wp.element;\r\n\r\n// import Spinner component\r\nconst { Spinner } = wp.components;\r\n\r\n// Add custom attribute to core/image and generateblocks/media blocks\r\nwp.hooks.addFilter(\r\n 'blocks.registerBlockType',\r\n 'squeeze/add-compressing-attribute',\r\n (settings, name) => {\r\n if (name === 'core/image' || name === 'generateblocks/media') {\r\n return {\r\n ...settings,\r\n attributes: {\r\n ...settings.attributes,\r\n isSqueezing: {\r\n type: 'boolean',\r\n default: false,\r\n },\r\n },\r\n };\r\n }\r\n return settings;\r\n }\r\n);\r\n\r\nconst withImageOnSelect = createHigherOrderComponent(\r\n function (BlockEdit) {\r\n return function (props) {\r\n const { attributes } = props;\r\n // Only target the core/image block\r\n if (props.name !== 'core/image' && props.name !== 'generateblocks/media') {\r\n // If not the core/image block, return the original BlockEdit\r\n return el(BlockEdit, props);\r\n }\r\n\r\n const { isSqueezing } = attributes;\r\n\r\n // While compressing, render the original editor plus a Spinner overlay\r\n if (isSqueezing) {\r\n\r\n return el(\r\n Fragment,\r\n {},\r\n // The normal block edit, dimmed\r\n el(\r\n 'div',\r\n { style: { position: 'relative' } },\r\n el(BlockEdit, props),\r\n el(\r\n 'div',\r\n { style: { position: 'absolute', backgroundColor: 'rgba(255,255,255,0.5)', pointerEvents: 'none', width: '100%', height: '100%', left: '0%', top: '0%' } },\r\n ),\r\n // Spinner absolutely centered\r\n el(\r\n 'div',\r\n {\r\n style: {\r\n position: 'absolute',\r\n top: '50%',\r\n left: '50%',\r\n transform: 'translate(-50%, -50%)',\r\n pointerEvents: 'none',\r\n },\r\n },\r\n el(Spinner, { size: 50 }),\r\n __('Squeezing image…', 'squeeze')\r\n )\r\n ),\r\n\r\n );\r\n }\r\n\r\n return el(BlockEdit, props);\r\n\r\n };\r\n },\r\n 'withImageOnSelect'\r\n);\r\n\r\n// Inject our HOC into Gutenberg’s BlockEdit pipeline\r\naddFilter(\r\n 'editor.BlockEdit',\r\n 'squeeze/with-image-onselect',\r\n withImageOnSelect\r\n);\r\n\r\n/**\r\n * Handles the compression of thumbnails after the image has been uploaded.\r\n * This function checks if the file is an image, determines if it should be compressed,\r\n * fetches the attachment data, and then compresses and uploads the image.\r\n * It also handles the original file and WebP conversion if specified in the options.\r\n * @param {object} file - The file attachment object.\r\n * @returns {Promise<void>}\r\n */\r\nasync function handleCompressThumbs(file) {\r\n const fileType = file.mime_type.split('/')[0];\r\n const fileSubType = file.mime_type.split('/')[1];\r\n\r\n if (!_helpers_js__WEBPACK_IMPORTED_MODULE_1__.maybeCompressAttachment(fileType, fileSubType, compressOptions)) {\r\n return;\r\n }\r\n\r\n //console.log('Compressing image:', file.id, file.source_url);\r\n window.onbeforeunload = _handlers_js__WEBPACK_IMPORTED_MODULE_2__.handleOnLeave;\r\n\r\n try {\r\n const attachmentResponse = await Squeeze.getAttachment(file.id);\r\n\r\n if (attachmentResponse.success === false) {\r\n window.onbeforeunload = null;\r\n console.error(sprintf(__('Error fetching attachment: %s', 'squeeze'), attachmentResponse.data));\r\n return;\r\n }\r\n\r\n const attachmentData = attachmentResponse.data;\r\n\r\n if (attachmentData.is_squeezed) {\r\n console.warn('Image already compressed:', attachmentData.id);\r\n window.onbeforeunload = null;\r\n return;\r\n }\r\n\r\n const attachment = {\r\n attributes: {\r\n url: attachmentData.url,\r\n mime: attachmentData.mime,\r\n name: attachmentData.name,\r\n filename: attachmentData.filename,\r\n id: attachmentData.id,\r\n sizes: attachmentData.sizes,\r\n }\r\n }\r\n\r\n _helpers_js__WEBPACK_IMPORTED_MODULE_1__.extendAttachment(attachment, file);\r\n\r\n const compressData = await Squeeze.handleCompress(attachment);\r\n const uploadData = await Squeeze.handleUpload({ attachment, base64: compressData });\r\n\r\n if (uploadData.success) {\r\n //console.log('Image uploaded successfully:', uploadData);\r\n window.onbeforeunload = null;\r\n } else {\r\n window.onbeforeunload = null;\r\n console.error(sprintf(__('Error uploading image: %s', 'squeeze'), uploadData.data));\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n window.onbeforeunload = null;\r\n }\r\n}\r\n\r\n/**\r\n * Walk through all blocks and compress thumbnails for the given media ID.\r\n * This function checks if the block or any of its inner blocks matches the media ID,\r\n * and if so, it compresses the thumbnails using handleCompressThumbs.\r\n * @param {integer} mediaId - The ID of the media item to compress.\r\n * @param {object} response - The response object containing the media data.\r\n * @returns {Promise<void>}\r\n */\r\nfunction squeezeAndUpdateThumbs(mediaId, response) {\r\n //console.log('squeezeAndUpdateThumbs', mediaId, response);\r\n return new Promise((resolve) => {\r\n setTimeout(() => {\r\n const blocks = wp.data.select('core/block-editor').getBlocks();\r\n // collect all handleCompressThumbs() calls\r\n const all = blocks.map(block => {\r\n // Check if the block or any of its innerBlocks matches the mediaId\r\n const matchesMedia = (block.attributes.id === mediaId) || (block.attributes.mediaId === mediaId);\r\n\r\n // Helper to recursively check innerBlocks\r\n function hasMatchingInnerBlock(innerBlocks) {\r\n if (!innerBlocks || !innerBlocks.length) return false;\r\n return innerBlocks.some(innerBlock => {\r\n if (\r\n innerBlock.attributes.id === mediaId ||\r\n innerBlock.attributes.mediaId === mediaId\r\n ) {\r\n return true;\r\n }\r\n return hasMatchingInnerBlock(innerBlock.innerBlocks);\r\n });\r\n }\r\n\r\n const isMatchingInnerBlock = hasMatchingInnerBlock(block.innerBlocks);\r\n\r\n if (!matchesMedia && !isMatchingInnerBlock) {\r\n return null;\r\n }\r\n\r\n // mark squeezing start\r\n if (isMatchingInnerBlock) {\r\n block.innerBlocks.forEach(innerBlock => {\r\n if (innerBlock.attributes.id === mediaId || innerBlock.attributes.mediaId === mediaId) {\r\n wp.data.dispatch('core/block-editor').updateBlockAttributes(innerBlock.clientId, { isSqueezing: true });\r\n }\r\n });\r\n } else {\r\n wp.data.dispatch('core/block-editor')\r\n .updateBlockAttributes(block.clientId, { isSqueezing: true });\r\n }\r\n // compress, then mark squeezing end\r\n return handleCompressThumbs(response)\r\n .then(() => {\r\n if (isMatchingInnerBlock) {\r\n block.innerBlocks.forEach(innerBlock => {\r\n if (innerBlock.attributes.id === mediaId || innerBlock.attributes.mediaId === mediaId) {\r\n wp.data.dispatch('core/block-editor').updateBlockAttributes(innerBlock.clientId, { isSqueezing: false });\r\n }\r\n });\r\n } else {\r\n wp.data.dispatch('core/block-editor')\r\n .updateBlockAttributes(block.clientId, { isSqueezing: false });\r\n }\r\n });\r\n }).filter(p => p); // remove nulls\r\n \r\n // when all compress‐promises settle (or immediately, if none)\r\n Promise.all(all).then(() => {\r\n resolve();\r\n });\r\n }, 100); // ensure attributes are ready\r\n });\r\n}\r\n\r\n// Compress the original before upload and thumbnails after upload\r\nwp.domReady(function () {\r\n // Middleware to intercept and modify the upload request\r\n wp.apiFetch.use(async (options, next) => {\r\n if (options.method === 'POST' && options.path.startsWith('/wp/v2/media')) {\r\n\r\n const formData = options.body;\r\n\r\n if (!(formData instanceof FormData) || !formData.has('file')) {\r\n return next(options);\r\n }\r\n\r\n const file = formData.get('file');\r\n\r\n if (file && !file.type.startsWith('image/')) {\r\n //console.log('Not an image file:', file);\r\n return next(options); // Proceed without modification\r\n }\r\n\r\n const fileType = file.type.split('/')[0];\r\n const fileSubType = file.type.split('/')[1];\r\n let originalFile = file;\r\n\r\n if (!_helpers_js__WEBPACK_IMPORTED_MODULE_1__.maybeCompressAttachment(fileType, fileSubType, compressOptions)) {\r\n //console.log('Compression not needed for:', file.name);\r\n return next(options); // Proceed without modification\r\n }\r\n\r\n \r\n\r\n const noticeId = 'compression-notice';\r\n // Show compression notice\r\n wp.data.dispatch('core/notices').createNotice(\r\n 'info',\r\n __('Squeezing the image...', 'squeeze'),\r\n { id: noticeId, isDismissible: false }\r\n )\r\n\r\n try {\r\n //console.time('Compressing image:', file.name);\r\n window.onbeforeunload = _handlers_js__WEBPACK_IMPORTED_MODULE_2__.handleOnLeave;\r\n\r\n // Compress the file\r\n //console.log('Compressing image:', file.name, file.size, file.type);\r\n const base64Obj = await Squeeze.compressBeforeUpload(file);\r\n if (!base64Obj || !base64Obj.base64) {\r\n console.warn('Compression skipped or failed for:', file.name, base64Obj);\r\n return next(options); // Proceed without modification\r\n }\r\n\r\n if (compressOptions.backup_original) {\r\n //console.log('Backing up original file:', file.name);\r\n const originalBlob = file instanceof Blob ? file : new Blob([file], { type: file.type });\r\n const isDirectWebp = compressOptions?.direct_webp && file.type !== 'image/webp';\r\n // If direct WebP conversion is enabled, convert the original file to WebP\r\n originalFile = isDirectWebp ? await Squeeze.convertFileToWebp(originalBlob) : originalBlob; // Use the original file from the attachment\r\n }\r\n\r\n const newFormData = new FormData();\r\n const imageBlob = _helpers_js__WEBPACK_IMPORTED_MODULE_1__.base64ToBlob(base64Obj.base64, file.type);\r\n\r\n newFormData.append('file', imageBlob, file.name);\r\n\r\n // Preserve other FormData fields (e.g., title, alt)\r\n for (const [key, value] of options.body.entries()) {\r\n if (key !== 'file') {\r\n newFormData.append(key, value);\r\n }\r\n }\r\n\r\n //console.log('New FormData:', newFormData);\r\n\r\n options.body = newFormData;\r\n\r\n const response = await next(options);\r\n\r\n if (response && response.id && response.media_type === 'image') {\r\n \r\n response.base64 = base64Obj.base64; // Add the base64 data to the response\r\n response.base64Webp = base64Obj?.base64Webp; // Add the WebP base64 data to the response\r\n if (compressOptions.backup_original) {\r\n response.originalFile = originalFile; // Add the original file to the response\r\n }\r\n\r\n const mediaId = response.id;\r\n squeezeAndUpdateThumbs(mediaId, response)\r\n .then(() => {\r\n // Dismiss the compression notice\r\n wp.data.dispatch('core/notices').removeNotice('compression-notice');\r\n window.onbeforeunload = null;\r\n //console.timeEnd('Compressing image:', file.name);\r\n })\r\n .catch(error => {\r\n console.error('Error during squeeze and update:', error);\r\n // Show error notice\r\n wp.data.dispatch('core/notices').createNotice(\r\n 'error',\r\n sprintf(__('Error squeezing the image: %s', 'squeeze'), error.message),\r\n { id: noticeId, isDismissible: true }\r\n );\r\n window.onbeforeunload = null;\r\n });\r\n\r\n }\r\n\r\n return response; // Return the modified response\r\n\r\n } catch (error) {\r\n wp.data.dispatch('core/notices').removeNotice('compression-notice');\r\n console.error('Error during image compression:', error);\r\n // Show error notice\r\n wp.data.dispatch('core/notices').createNotice(\r\n 'error',\r\n sprintf(__('Error squeezing the image: %s', 'squeeze'), error?.message || error),\r\n { id: noticeId, isDismissible: true }\r\n );\r\n window.onbeforeunload = null;\r\n return next(options); // Proceed without modification\r\n }\r\n }\r\n\r\n returnnext(options);\r\n\r\n });\r\n});\n\n//# sourceURL=webpack:///./assets/js/editor.js?");19 eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _squeeze_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./squeeze.js */ \"./assets/js/squeeze.js\");\n/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./helpers.js */ \"./assets/js/helpers.js\");\n/* harmony import */ var _handlers_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./handlers.js */ \"./assets/js/handlers.js\");\n\r\n\r\n\r\n\r\n\"use strict\";\r\n\r\nconst { sprintf, __ } = wp.i18n; // Import __() from wp.i18n\r\n\r\nconst Squeeze = new _squeeze_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"](squeezeOptions);\r\nconst compressOptions = JSON.parse(squeezeOptions.options);\r\n\r\nconst addFilter = wp.hooks.addFilter;\r\nconst createHigherOrderComponent = wp.compose.createHigherOrderComponent;\r\nconst { createElement: el, Fragment } = wp.element;\r\n\r\n// import Spinner component\r\nconst { Spinner } = wp.components;\r\n\r\n// Add custom attribute to core/image and generateblocks/media blocks\r\nwp.hooks.addFilter(\r\n 'blocks.registerBlockType',\r\n 'squeeze/add-compressing-attribute',\r\n (settings, name) => {\r\n if (name === 'core/image' || name === 'generateblocks/media') {\r\n return {\r\n ...settings,\r\n attributes: {\r\n ...settings.attributes,\r\n isSqueezing: {\r\n type: 'boolean',\r\n default: false,\r\n },\r\n },\r\n };\r\n }\r\n return settings;\r\n }\r\n);\r\n\r\nconst withImageOnSelect = createHigherOrderComponent(\r\n function (BlockEdit) {\r\n return function (props) {\r\n const { attributes } = props;\r\n // Only target the core/image block\r\n if (props.name !== 'core/image' && props.name !== 'generateblocks/media') {\r\n // If not the core/image block, return the original BlockEdit\r\n return el(BlockEdit, props);\r\n }\r\n\r\n const { isSqueezing } = attributes;\r\n\r\n // While compressing, render the original editor plus a Spinner overlay\r\n if (isSqueezing) {\r\n\r\n return el(\r\n Fragment,\r\n {},\r\n // The normal block edit, dimmed\r\n el(\r\n 'div',\r\n { style: { position: 'relative' } },\r\n el(BlockEdit, props),\r\n el(\r\n 'div',\r\n { style: { position: 'absolute', backgroundColor: 'rgba(255,255,255,0.5)', pointerEvents: 'none', width: '100%', height: '100%', left: '0%', top: '0%' } },\r\n ),\r\n // Spinner absolutely centered\r\n el(\r\n 'div',\r\n {\r\n style: {\r\n position: 'absolute',\r\n top: '50%',\r\n left: '50%',\r\n transform: 'translate(-50%, -50%)',\r\n pointerEvents: 'none',\r\n },\r\n },\r\n el(Spinner, { size: 50 }),\r\n __('Squeezing image…', 'squeeze')\r\n )\r\n ),\r\n\r\n );\r\n }\r\n\r\n return el(BlockEdit, props);\r\n\r\n };\r\n },\r\n 'withImageOnSelect'\r\n);\r\n\r\n// Inject our HOC into Gutenberg’s BlockEdit pipeline\r\naddFilter(\r\n 'editor.BlockEdit',\r\n 'squeeze/with-image-onselect',\r\n withImageOnSelect\r\n);\r\n\r\n/**\r\n * Handles the compression of thumbnails after the image has been uploaded.\r\n * This function checks if the file is an image, determines if it should be compressed,\r\n * fetches the attachment data, and then compresses and uploads the image.\r\n * It also handles the original file and WebP conversion if specified in the options.\r\n * @param {object} file - The file attachment object.\r\n * @returns {Promise<void>}\r\n */\r\nasync function handleCompressThumbs(file) {\r\n const fileType = file.mime_type.split('/')[0];\r\n const fileSubType = file.mime_type.split('/')[1];\r\n\r\n if (!_helpers_js__WEBPACK_IMPORTED_MODULE_1__.maybeCompressAttachment(fileType, fileSubType, compressOptions)) {\r\n return;\r\n }\r\n\r\n //console.log('Compressing image:', file.id, file.source_url);\r\n window.onbeforeunload = _handlers_js__WEBPACK_IMPORTED_MODULE_2__.handleOnLeave;\r\n\r\n try {\r\n const attachmentResponse = await Squeeze.getAttachment(file.id);\r\n\r\n if (attachmentResponse.success === false) {\r\n window.onbeforeunload = null;\r\n console.error(sprintf(__('Error fetching attachment: %s', 'squeeze'), attachmentResponse.data));\r\n return;\r\n }\r\n\r\n const attachmentData = attachmentResponse.data;\r\n\r\n if (attachmentData.is_squeezed) {\r\n console.warn('Image already compressed:', attachmentData.id);\r\n window.onbeforeunload = null;\r\n return;\r\n }\r\n\r\n const attachment = {\r\n attributes: {\r\n url: attachmentData.url,\r\n mime: attachmentData.mime,\r\n name: attachmentData.name,\r\n filename: attachmentData.filename,\r\n id: attachmentData.id,\r\n sizes: attachmentData.sizes,\r\n }\r\n }\r\n\r\n _helpers_js__WEBPACK_IMPORTED_MODULE_1__.extendAttachment(attachment, file);\r\n\r\n const compressData = await Squeeze.handleCompress(attachment);\r\n const uploadData = await Squeeze.handleUpload({ attachment, base64: compressData });\r\n\r\n if (uploadData.success) {\r\n //console.log('Image uploaded successfully:', uploadData);\r\n window.onbeforeunload = null;\r\n } else {\r\n window.onbeforeunload = null;\r\n console.error(sprintf(__('Error uploading image: %s', 'squeeze'), uploadData.data));\r\n }\r\n } catch (error) {\r\n console.error(error);\r\n window.onbeforeunload = null;\r\n }\r\n}\r\n\r\n/**\r\n * Walk through all blocks and compress thumbnails for the given media ID.\r\n * This function checks if the block or any of its inner blocks matches the media ID,\r\n * and if so, it compresses the thumbnails using handleCompressThumbs.\r\n * @param {integer} mediaId - The ID of the media item to compress.\r\n * @param {object} response - The response object containing the media data.\r\n * @returns {Promise<void>}\r\n */\r\nfunction squeezeAndUpdateThumbs(mediaId, response) {\r\n //console.log('squeezeAndUpdateThumbs', mediaId, response);\r\n return new Promise((resolve) => {\r\n setTimeout(() => {\r\n const blocks = wp.data.select('core/block-editor').getBlocks();\r\n // collect all handleCompressThumbs() calls\r\n const all = blocks.map(block => {\r\n // Check if the block or any of its innerBlocks matches the mediaId\r\n const matchesMedia = (block.attributes.id === mediaId) || (block.attributes.mediaId === mediaId);\r\n\r\n // Helper to recursively check innerBlocks\r\n function hasMatchingInnerBlock(innerBlocks) {\r\n if (!innerBlocks || !innerBlocks.length) return false;\r\n return innerBlocks.some(innerBlock => {\r\n if (\r\n innerBlock.attributes.id === mediaId ||\r\n innerBlock.attributes.mediaId === mediaId\r\n ) {\r\n return true;\r\n }\r\n return hasMatchingInnerBlock(innerBlock.innerBlocks);\r\n });\r\n }\r\n\r\n const isMatchingInnerBlock = hasMatchingInnerBlock(block.innerBlocks);\r\n\r\n if (!matchesMedia && !isMatchingInnerBlock) {\r\n return null;\r\n }\r\n\r\n // mark squeezing start\r\n if (isMatchingInnerBlock) {\r\n block.innerBlocks.forEach(innerBlock => {\r\n if (innerBlock.attributes.id === mediaId || innerBlock.attributes.mediaId === mediaId) {\r\n wp.data.dispatch('core/block-editor').updateBlockAttributes(innerBlock.clientId, { isSqueezing: true });\r\n }\r\n });\r\n } else {\r\n wp.data.dispatch('core/block-editor')\r\n .updateBlockAttributes(block.clientId, { isSqueezing: true });\r\n }\r\n // compress, then mark squeezing end\r\n return handleCompressThumbs(response)\r\n .then(() => {\r\n if (isMatchingInnerBlock) {\r\n block.innerBlocks.forEach(innerBlock => {\r\n if (innerBlock.attributes.id === mediaId || innerBlock.attributes.mediaId === mediaId) {\r\n wp.data.dispatch('core/block-editor').updateBlockAttributes(innerBlock.clientId, { isSqueezing: false });\r\n }\r\n });\r\n } else {\r\n wp.data.dispatch('core/block-editor')\r\n .updateBlockAttributes(block.clientId, { isSqueezing: false });\r\n }\r\n });\r\n }).filter(p => p); // remove nulls\r\n \r\n // when all compress‐promises settle (or immediately, if none)\r\n Promise.all(all).then(() => {\r\n resolve();\r\n });\r\n }, 100); // ensure attributes are ready\r\n });\r\n}\r\n\r\n// Compress the original before upload and thumbnails after upload\r\nwp.domReady(function () {\r\n // Middleware to intercept and modify the upload request\r\n wp.apiFetch.use(async (options, next) => {\r\n // Skip if we already marked this request as processed by Squeeze\r\n if (options && options._squeezeProcessed) {\r\n return await next(options);\r\n }\r\n \r\n // ✅ Only intercept media uploads with a file\r\n if (options.method === 'POST' && options.path?.startsWith('/wp/v2/media')) { // <== options.path?. is crucial here!!!\r\n\r\n const formData = options.body;\r\n\r\n if (!(formData instanceof FormData) || !formData.has('file')) {\r\n return await next(options);\r\n }\r\n\r\n // ✅ Only compress uploads for the *current post in the editor*\r\n const currentPostId = wp?.data?.select?.('core/editor')?.getCurrentPostId?.();\r\n const postValue = formData.get('post');\r\n if (!postValue || Number(postValue) !== Number(currentPostId)) {\r\n return await next(options);\r\n }\r\n\r\n const file = formData.get('file');\r\n\r\n if (file && !file.type.startsWith('image/')) {\r\n //console.log('Not an image file:', file);\r\n return await next(options); // Proceed without modification\r\n }\r\n\r\n const fileType = file.type.split('/')[0];\r\n const fileSubType = file.type.split('/')[1];\r\n let originalFile = file;\r\n\r\n if (!_helpers_js__WEBPACK_IMPORTED_MODULE_1__.maybeCompressAttachment(fileType, fileSubType, compressOptions)) {\r\n //console.log('Compression not needed for:', file.name);\r\n return await next(options); // Proceed without modification\r\n }\r\n\r\n \r\n\r\n const noticeId = 'compression-notice';\r\n // Show compression notice\r\n wp.data.dispatch('core/notices').createNotice(\r\n 'info',\r\n __('Squeezing the image...', 'squeeze'),\r\n { id: noticeId, isDismissible: false }\r\n )\r\n\r\n // Ensure the browser can set multipart boundary for FormData\r\n if (options.headers) {\r\n delete options.headers['Content-Type'];\r\n delete options.headers['content-type'];\r\n }\r\n\r\n // Mark this outgoing request so anything we intentionally trigger can skip the middleware.\r\n options._squeezeProcessed = true;\r\n\r\n try {\r\n //console.time('Compressing image:', file.name);\r\n window.onbeforeunload = _handlers_js__WEBPACK_IMPORTED_MODULE_2__.handleOnLeave;\r\n\r\n // Compress the file\r\n //console.log('Compressing image:', file.name, file.size, file.type);\r\n const base64Obj = await Squeeze.compressBeforeUpload(file);\r\n if (!base64Obj || !base64Obj.base64) {\r\n console.warn('Compression skipped or failed for:', file.name, base64Obj);\r\n return await next(options); // Proceed without modification\r\n }\r\n\r\n if (compressOptions.backup_original) {\r\n //console.log('Backing up original file:', file.name);\r\n const originalBlob = file instanceof Blob ? file : new Blob([file], { type: file.type });\r\n const isDirectWebp = compressOptions?.direct_webp && file.type !== 'image/webp';\r\n // If direct WebP conversion is enabled, convert the original file to WebP\r\n originalFile = isDirectWebp ? await Squeeze.convertFileToWebp(originalBlob) : originalBlob; // Use the original file from the attachment\r\n }\r\n\r\n const newFormData = new FormData();\r\n const imageBlob = _helpers_js__WEBPACK_IMPORTED_MODULE_1__.base64ToBlob(base64Obj.base64, file.type);\r\n\r\n newFormData.append('file', imageBlob, file.name);\r\n\r\n // Preserve other FormData fields (e.g., title, alt)\r\n for (const [key, value] of options.body.entries()) {\r\n if (key !== 'file') {\r\n newFormData.append(key, value);\r\n }\r\n }\r\n\r\n //console.log('New FormData:', newFormData);\r\n\r\n options.body = newFormData;\r\n\r\n const response = await next(options);\r\n\r\n if (response && response.id && response.media_type === 'image') {\r\n \r\n response.base64 = base64Obj.base64; // Add the base64 data to the response\r\n response.base64Webp = base64Obj?.base64Webp; // Add the WebP base64 data to the response\r\n if (compressOptions.backup_original) {\r\n response.originalFile = originalFile; // Add the original file to the response\r\n }\r\n\r\n const mediaId = response.id;\r\n squeezeAndUpdateThumbs(mediaId, response)\r\n .then(() => {\r\n // Dismiss the compression notice\r\n wp.data.dispatch('core/notices').removeNotice('compression-notice');\r\n window.onbeforeunload = null;\r\n //console.timeEnd('Compressing image:', file.name);\r\n })\r\n .catch(error => {\r\n console.error('Error during squeeze and update:', error);\r\n // Show error notice\r\n wp.data.dispatch('core/notices').createNotice(\r\n 'error',\r\n sprintf(__('Error squeezing the image: %s', 'squeeze'), error.message),\r\n { id: noticeId, isDismissible: true }\r\n );\r\n window.onbeforeunload = null;\r\n });\r\n\r\n }\r\n\r\n return response; // Return the modified response\r\n\r\n } catch (error) {\r\n wp.data.dispatch('core/notices').removeNotice('compression-notice');\r\n console.error('Error during image compression:', error);\r\n // Show error notice\r\n wp.data.dispatch('core/notices').createNotice(\r\n 'error',\r\n sprintf(__('Error squeezing the image: %s', 'squeeze'), error?.message || error),\r\n { id: noticeId, isDismissible: true }\r\n );\r\n window.onbeforeunload = null;\r\n return await next(options); // Proceed without modification\r\n }\r\n }\r\n\r\n // Default: let request through untouched\r\n return await next(options);\r\n\r\n });\r\n});\n\n//# sourceURL=webpack:///./assets/js/editor.js?"); 20 20 21 21 /***/ }), … … 47 47 /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 48 48 49 eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ SQUEEZE)\n/* harmony export */ });\n/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./helpers.js */ \"./assets/js/helpers.js\");\n/* harmony import */ var browser_image_compression__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! browser-image-compression */ \"./node_modules/browser-image-compression/dist/browser-image-compression.mjs\");\n/* harmony import */ var _jsquash_webp__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @jsquash/webp */ \"./node_modules/@jsquash/webp/encode.js\");\n\r\n\r\n\r\n\r\n\r\n\r\nconst { __ } = wp.i18n; // Import __() from wp.i18n\r\n\r\nclass SQUEEZE {\r\n\r\n constructor(squeeze) {\r\n this.options = JSON.parse(squeeze.options); // plugin options\r\n this.nonce = squeeze.nonce; // nonce\r\n this.ajaxUrl = squeeze.ajaxUrl; // ajax url\r\n this.timeout = parseInt(this.options.timeout) * 1000; // convert to milliseconds\r\n this.poolSize = navigator.hardwareConcurrency || 1; // number of threads\r\n }\r\n\r\n handleCompress = async ( attachment, type = 'uncompressed' ) => {\r\n const attachmentData = attachment.attributes;\r\n const url = attachmentData?.originalImageURL ?? attachmentData.url;\r\n const mime = attachmentData.mime;\r\n const name = attachmentData.name;\r\n const filename = attachmentData?.originalImageName ?? attachmentData.filename;\r\n const attachmentID = attachmentData.id;\r\n const sizes = attachmentData.sizes;\r\n const format = mime.split(\"/\")[1];\r\n const sourceType = format;\r\n const outputType = format;\r\n const skipFull = attachmentData?.skipFull ?? attachmentData.originalImageName === undefined ? true : false;\r\n const timeout = this.timeout;\r\n const isPreview = attachmentData?.isPreview ?? false;\r\n const file = attachmentData?.file ?? null; // the original file, it passed for the newly uploaded attachment to compress it\r\n\r\n // the original compressed image base64, it passed when the attachment was already compressed and we need to compress thumbs only\r\n // we pass it in order to prevent re-compressing the original image when we need to compress thumbs only\r\n const base64Compressed = attachmentData?.base64Compressed ?? null; \r\n const base64WebpCompressed = attachmentData?.base64WebpCompressed ?? null; // the original compressed webp image base64\r\n\r\n const worker = new Worker(new URL(/* worker import */ __webpack_require__.p + __webpack_require__.u(\"assets_js_worker_js\"), __webpack_require__.b), {type: undefined}); // worker url\r\n const channel = new MessageChannel();\r\n worker.postMessage({\r\n action: 'compress',\r\n format,\r\n url,\r\n name,\r\n sourceType,\r\n outputType,\r\n mime,\r\n sizes,\r\n skipFull,\r\n timeout,\r\n isPreview,\r\n file,\r\n base64Compressed,\r\n base64WebpCompressed,\r\n type,\r\n options: this.options,\r\n //urlOriginal: urlOriginal,\r\n }, [channel.port2]);\r\n\r\n // Listen for compression requests from the worker via port1\r\n channel.port1.onmessage = async (ev) => {\r\n //console.log(\"Worker received compression request:\", ev.data);\r\n const { id, action, fileOrArrayBuffer, mime, options } = ev.data;\r\n if (action !== 'imageCompression') return;\r\n\r\n try {\r\n // Accept ArrayBuffer or Blob\r\n const blob = (fileOrArrayBuffer instanceof ArrayBuffer)\r\n ? new Blob([this.toUint8Array(fileOrArrayBuffer)], { type: mime })\r\n : fileOrArrayBuffer;\r\n\r\n const compressedBlob = await (0,browser_image_compression__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(blob, { useWebWorker: true, ...(options || {}) });\r\n\r\n // quick checks\r\n //console.log('compressedBlob.type, size:', compressedBlob.type, compressedBlob.size);\r\n // normalize final binary\r\n const compressedArrayBuffer = await compressedBlob.arrayBuffer();\r\n\r\n // Reply with the compressed ArrayBuffer (transfer to avoid copy)\r\n channel.port1.postMessage({ id, ok: true, arrayBuffer: compressedArrayBuffer }, [compressedArrayBuffer]);\r\n } catch (err) {\r\n channel.port1.postMessage({ id, ok: false, error: err.message || String(err) });\r\n }\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const timeoutId = setTimeout(() => {\r\n worker.terminate();\r\n console.warn('Worker compress terminated', name, sourceType, outputType);\r\n reject(__('Request timed out.', 'squeeze'));\r\n }, timeout);\r\n\r\n worker.onmessage = (event) => {\r\n clearTimeout(timeoutId);\r\n if (event.data.error) {\r\n reject(event.data.error);\r\n } else {\r\n resolve(event.data);\r\n }\r\n worker.terminate();\r\n };\r\n\r\n worker.onerror = (error) => {\r\n clearTimeout(timeoutId);\r\n reject(`Worker error: ${error.message}`);\r\n };\r\n });\r\n\r\n }\r\n\r\n handleUpload = async ({ attachment, base64, type = 'uncompressed', mediaIDs = [] }) => {\r\n\r\n const attachmentData = attachment.attributes;\r\n const url = attachmentData?.originalImageURL ?? attachmentData.url;\r\n const mime = attachmentData.mime;\r\n const filename = attachmentData?.originalImageName ?? attachmentData.filename;\r\n const attachmentID = attachmentData.id;\r\n const format = base64?.isDirectWebp && type !== 'path' ? 'webp' : mime.split(\"/\")[1];\r\n const sizes = attachmentData.sizes;\r\n\r\n const isDirectWebp = base64?.isDirectWebp && mime !== 'image/webp';\r\n const isBackupOriginal = this.options?.backup_original ?? false; // check if backup original is enabled\r\n let originalFile = attachmentData?.originalFile ?? null; // the original file, used for creating backup\r\n \r\n // if originalFile is not provided, try to get it from the URL\r\n // used when the attachment needs to be converted to webp from jpg or png\r\n if (!originalFile && type !== 'path' && isBackupOriginal && isDirectWebp) {\r\n const file = await (0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.getFileFromUrl)(url, filename, mime.split(\"/\")[1])\r\n //console.log('conveting original file to webp', file);\r\n originalFile = await this.convertFileToWebp(file);\r\n }\r\n //console.log('handleUpload attachmentData', attachmentData)\r\n\r\n const data = {\r\n action: 'squeeze_update_attachment',\r\n _ajax_nonce: this.nonce,\r\n filename: filename,\r\n type: 'image',\r\n format: format,\r\n base64: base64.base64,\r\n base64Sizes: base64.base64Sizes,\r\n base64Webp: base64.base64Webp,\r\n base64SizesWebp: base64.base64SizesWebp,\r\n attachmentID: attachmentID,\r\n url: url,\r\n process: type,\r\n originalFile: originalFile,\r\n }\r\n\r\n const formData = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.objectToFormData)(data);\r\n\r\n //console.log('squeeze_update_attachment', JSON.stringify(data, null, 2));\r\n\r\n try {\r\n const uploadResponse = await jQuery.ajax({\r\n url: this.ajaxUrl, // + '111',\r\n type: 'POST',\r\n data: formData,\r\n processData: false, // very important!\r\n contentType: false, // very important!\r\n });\r\n\r\n //if (uploadResponse.success) {\r\n uploadResponse['mediaIDs'] = mediaIDs;\r\n //}\r\n\r\n if (!uploadResponse?.data?.url) {\r\n uploadResponse.url = url; // fallback to original URL if not provided\r\n }\r\n\r\n //console.log('uploadResponse', JSON.stringify(uploadResponse, null, 2));\r\n\r\n return uploadResponse;\r\n \r\n } catch (error) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': error.message,\r\n 'success': false\r\n };\r\n }\r\n\r\n }\r\n\r\n handleBulkUpload = async (type = 'uncompressed', mediaIDs = []) => {\r\n let currentID;\r\n let attachment;\r\n\r\n switch (type) {\r\n case 'all':\r\n case 'uncompressed':\r\n currentID = mediaIDs[0];\r\n break;\r\n case 'path':\r\n currentID = mediaIDs[0]?.filename;\r\n break;\r\n default:\r\n currentID = 0;\r\n break;\r\n }\r\n\r\n if (type === 'path') {\r\n\r\n \r\n\r\n attachment = {\r\n attributes: {\r\n url: mediaIDs[0].url,\r\n mime: mediaIDs[0].mime,\r\n name: mediaIDs[0].name,\r\n filename: mediaIDs[0].filename,\r\n id: mediaIDs[0].id,\r\n sizes: mediaIDs[0]?.sizes,\r\n }\r\n }\r\n\r\n } else {\r\n\r\n const attachmentResponse = await this.getAttachment(currentID); \r\n if (attachmentResponse.success === false) {\r\n if (Array.isArray(mediaIDs)) mediaIDs.shift();\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': attachmentResponse.data\r\n }\r\n }\r\n const attachmentData = attachmentResponse.data;\r\n attachment = {\r\n attributes: {\r\n url: attachmentData.url,\r\n mime: attachmentData.mime,\r\n name: attachmentData.name,\r\n filename: attachmentData.filename,\r\n id: attachmentData.id,\r\n sizes: attachmentData.sizes,\r\n }\r\n }\r\n\r\n }\r\n\r\n if (Array.isArray(mediaIDs)) mediaIDs.shift();\r\n\r\n const mediaType = attachment.attributes.mime.split(\"/\")[0];\r\n const mediaSubType = attachment.attributes.mime.split(\"/\")[1];\r\n\r\n if (!(0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.maybeCompressAttachment)(mediaType, mediaSubType)) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': __('Skipped', 'squeeze')\r\n }\r\n }\r\n\r\n try {\r\n const compressData = await this.handleCompress( attachment, type );\r\n const uploadData = await this.handleUpload({ attachment: attachment, base64: compressData, type: type, mediaIDs: mediaIDs })\r\n\r\n return uploadData;\r\n\r\n } catch (error) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': error?.message ?? error,\r\n 'success': false\r\n }\r\n }\r\n\r\n }\r\n\r\n \r\n\r\n handleRestore = async (attachmentID) => {\r\n const data = {\r\n action: 'squeeze_restore_attachment',\r\n _ajax_nonce: this.nonce,\r\n attachmentID: attachmentID,\r\n };\r\n\r\n const response = await jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n };\r\n\r\n // Get list of attachments by path\r\n getAttachmentsByPath = async (path) => {\r\n\r\n const data = {\r\n action: 'squeeze_get_attachment_by_path',\r\n path: path,\r\n _ajax_nonce: this.nonce,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getAttachment = async (attachmentID) => {\r\n const data = {\r\n action: 'squeeze_get_attachment',\r\n _ajax_nonce: this.nonce,\r\n attachmentID: attachmentID,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getNextAttachments = async (page = 1, type = 'uncompressed', lastId = 0) => {\r\n const data = {\r\n action: 'squeeze_get_next_attachments',\r\n _ajax_nonce: this.nonce,\r\n page: page,\r\n type: type,\r\n lastId: lastId,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getDirectories = async (parentDir = false) => {\r\n const data = {\r\n action: 'squeeze_get_directories',\r\n _ajax_nonce: this.nonce,\r\n }\r\n\r\n if (parentDir) {\r\n data['parentDir'] = parentDir;\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n \r\n\r\n setOptions = async (options) => {\r\n const data = {\r\n action: 'squeeze_set_options',\r\n _ajax_nonce: this.nonce,\r\n options: options,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n /**\r\n * Compresses an image before upload.\r\n * If the compressSizes parameter is provided, it will compress the sizes specified along with the original image.\r\n * \r\n * @param {File} file - The file to be compressed\r\n * @param {object} compressSizes - Optional sizes for compression\r\n * @returns {Promise<object|boolean>} - Returns a Promise that resolves to the compressed file as a base64 object or false if the file is invalid\r\n */\r\n compressBeforeUpload = async (file, compressSizes = null) => {\r\n if (!(file instanceof Blob) && !(file instanceof File)) {\r\n console.error('Invalid file type:', file);\r\n return false; // Return false if the file is not a Blob or File\r\n }\r\n\r\n const attachment = {\r\n attributes: {\r\n url: null,\r\n mime: file.type,\r\n name: file.name,\r\n filename: file.name,\r\n id: 0,\r\n sizes: compressSizes,\r\n file: file,\r\n skipFull: false,\r\n }\r\n }\r\n\r\n try {\r\n const base64Obj = await this.handleCompress(attachment);\r\n\r\n return base64Obj; // Return the compressed file as base64Obj\r\n } catch (error) {\r\n console.error(error);\r\n return error; // Return the error for further handling\r\n }\r\n }\r\n\r\n isWebpEncodingSupported = async () => {\r\n try {\r\n const canvas = document.createElement('canvas');\r\n if (!canvas.toDataURL) return false;\r\n const data = canvas.toDataURL('image/webp');\r\n // WebP signature starts with \"data:image/webp\"\r\n return data.indexOf('data:image/webp') === 0;\r\n } catch (e) {\r\n return false;\r\n }\r\n }\r\n\r\n handleConvertFileToWebp = async (args) => {\r\n const timeout = this.timeout;\r\n \r\n const { file, sourceType, name } = args;\r\n const fileBuffer = await file.arrayBuffer();\r\n const mime = sourceType === 'png' ? 'image/png' : 'image/jpeg';\r\n const inputBlob = new Blob([fileBuffer], { type: mime });\r\n // compression options you can tune\r\n const imageCompressionOptions = {\r\n useWebWorker: true,\r\n fileType: 'image/webp',\r\n };\r\n\r\n return new Promise(async (resolve, reject) => {\r\n const timeoutId = setTimeout(() => {\r\n console.warn('Worker convertToWebp terminated', name);\r\n reject(new Error(__('Request timed out.', 'squeeze')));\r\n }, timeout);\r\n\r\n try {\r\n\r\n const compressedBlob = await (0,browser_image_compression__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(inputBlob, imageCompressionOptions);\r\n\r\n // quick checks\r\n //console.log('compressedBlob.type, size:', compressedBlob.type, compressedBlob.size);\r\n // normalize final binary\r\n const webpArrayBuffer = await compressedBlob.arrayBuffer();\r\n\r\n const webpName = name.replace(/\\.\\w+$/, '.webp');\r\n const webpFile = new File([webpArrayBuffer], webpName, { type: 'image/webp', lastModified: Date.now() });\r\n\r\n clearTimeout(timeoutId);\r\n resolve({ webpFile }); // must return object with webpFile prop\r\n } catch (error) {\r\n clearTimeout(timeoutId);\r\n console.error('Error during image processing:', error);\r\n reject(error);\r\n }\r\n });\r\n \r\n }\r\n\r\n // helper: normalize buffer-like inputs to Uint8Array\r\n toUint8Array = (bufferLike) => {\r\n if (bufferLike instanceof Uint8Array) return bufferLike;\r\n if (bufferLike instanceof ArrayBuffer) return new Uint8Array(bufferLike);\r\n if (ArrayBuffer.isView(bufferLike)) return new Uint8Array(bufferLike.buffer, bufferLike.byteOffset, bufferLike.byteLength);\r\n // Node Buffer (in some environments) has .buffer and .byteOffset\r\n if (bufferLike && typeof bufferLike === 'object' && bufferLike.buffer) {\r\n return new Uint8Array(bufferLike.buffer, bufferLike.byteOffset || 0, bufferLike.byteLength || bufferLike.length);\r\n }\r\n throw new Error('Unsupported buffer type: ' + Object.prototype.toString.call(bufferLike));\r\n }\r\n\r\n /**\r\n * Convert an image File to WebP.\r\n *\r\n * @param {File} inputFile — the original image File (e.g. JPEG, PNG).\r\n * @returns {Promise<File>} — a Promise that resolves to a WebP File.\r\n */\r\n convertFileToWebp = async (inputFile) => {\r\n // Ensure it’s an image\r\n if (!inputFile.type.startsWith('image/')) {\r\n throw new Error('Input must be an image File');\r\n }\r\n const args = {\r\n file: inputFile,\r\n sourceType: inputFile.type.split('/')[1], // e.g. 'jpeg', 'png'\r\n name: inputFile.name,\r\n }\r\n const webpSupported = await this.isWebpEncodingSupported();\r\n try {\r\n // Convert the image to WebP\r\n let webpData;\r\n if (webpSupported) {\r\n webpData = await this.handleConvertFileToWebp(args);\r\n } else {\r\n console.warn('WebP encoding not supported on this device. Using original format instead.');\r\n const webpName = args.name.replace(/\\.\\w+$/, '.webp');\r\n webpData = await this.convertToWebpWithJsquash(inputFile, webpName);\r\n }\r\n if (!webpData || !webpData.webpFile) {\r\n throw new Error('Failed to convert image to WebP');\r\n }\r\n\r\n return webpData.webpFile; // Return the WebP File\r\n\r\n } catch (error) {\r\n console.error('Error converting image to WebP:', error);\r\n throw error; // Re-throw the error for further handling\r\n }\r\n\r\n }\r\n\r\n /**\r\n * Convert an input (File | Blob | ArrayBuffer | Uint8Array) to a WebP File using @jsquash/webp.\r\n * @param {File|Blob|ArrayBuffer|Uint8Array} input\r\n * @param {string} outputName - desired filename for the .webp output\r\n * @param {{quality?: number, lossless?: boolean, method?: number}} [opts] - encoder options (quality 0-100)\r\n * @returns {Promise<File>} - WebP File\r\n */\r\n convertToWebpWithJsquash = async (input, outputName = 'out.webp', opts = { quality: 100 }) => {\r\n // Normalize input to Blob\r\n let blob;\r\n if (input instanceof Blob) {\r\n blob = input;\r\n } else if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {\r\n blob = new Blob([input]);\r\n } else {\r\n throw new Error('Unsupported input type. Provide File, Blob, ArrayBuffer or TypedArray.');\r\n }\r\n\r\n // Load image into <img>\r\n const dataURL = await new Promise((resolve, reject) => {\r\n const fr = new FileReader();\r\n fr.onload = () => resolve(fr.result);\r\n fr.onerror = reject;\r\n fr.readAsDataURL(blob);\r\n });\r\n\r\n const img = await new Promise((resolve, reject) => {\r\n const i = new Image();\r\n i.onload = () => resolve(i);\r\n i.onerror = () => reject(new Error('Failed to load image for conversion'));\r\n i.src = dataURL;\r\n // avoid cross-origin taint if data-url is used\r\n });\r\n\r\n // Draw to canvas and extract ImageData\r\n const canvas = document.createElement('canvas');\r\n canvas.width = img.naturalWidth || img.width;\r\n canvas.height = img.naturalHeight || img.height;\r\n const ctx = canvas.getContext('2d');\r\n ctx.drawImage(img, 0, 0);\r\n const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\r\n\r\n // encode -> returns ArrayBuffer (WASM libwebp)\r\n // opts example: { quality: 75, lossless: false, method: 4 }\r\n const webpArrayBuffer = await (0,_jsquash_webp__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(imageData, opts);\r\n\r\n // Validate a little (size)\r\n if (!webpArrayBuffer || webpArrayBuffer.byteLength < 20) {\r\n throw new Error('jsquash produced an invalid WebP buffer');\r\n }\r\n\r\n // Wrap into File\r\n const webpFile = new File([webpArrayBuffer], outputName.replace(/\\.\\w+$/, '.webp'), {\r\n type: 'image/webp',\r\n lastModified: Date.now(),\r\n });\r\n\r\n return {webpFile};\r\n }\r\n\r\n}\n\n//# sourceURL=webpack:///./assets/js/squeeze.js?");49 eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ SQUEEZE)\n/* harmony export */ });\n/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./helpers.js */ \"./assets/js/helpers.js\");\n/* harmony import */ var browser_image_compression__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! browser-image-compression */ \"./node_modules/browser-image-compression/dist/browser-image-compression.mjs\");\n/* harmony import */ var _jsquash_webp__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @jsquash/webp */ \"./node_modules/@jsquash/webp/encode.js\");\n\r\n\r\n\r\n\r\n\r\n\r\nconst { __ } = wp.i18n; // Import __() from wp.i18n\r\n\r\nclass SQUEEZE {\r\n\r\n constructor(squeeze) {\r\n this.options = JSON.parse(squeeze.options); // plugin options\r\n this.nonce = squeeze.nonce; // nonce\r\n this.ajaxUrl = squeeze.ajaxUrl; // ajax url\r\n this.timeout = parseInt(this.options.timeout) * 1000; // convert to milliseconds\r\n this.poolSize = navigator.hardwareConcurrency || 1; // number of threads\r\n }\r\n\r\n handleCompress = async ( attachment, type = 'uncompressed' ) => {\r\n const attachmentData = attachment.attributes;\r\n const url = attachmentData?.originalImageURL ?? attachmentData.url;\r\n const mime = attachmentData.mime;\r\n const name = attachmentData.name;\r\n const filename = attachmentData?.originalImageName ?? attachmentData.filename;\r\n const attachmentID = attachmentData.id;\r\n const sizes = attachmentData.sizes;\r\n const format = mime.split(\"/\")[1];\r\n const sourceType = format;\r\n const outputType = format;\r\n const skipFull = attachmentData?.skipFull ?? attachmentData.originalImageName === undefined ? true : false;\r\n const timeout = this.timeout;\r\n const isPreview = attachmentData?.isPreview ?? false;\r\n const file = attachmentData?.file ?? null; // the original file, it passed for the newly uploaded attachment to compress it\r\n\r\n // the original compressed image base64, it passed when the attachment was already compressed and we need to compress thumbs only\r\n // we pass it in order to prevent re-compressing the original image when we need to compress thumbs only\r\n const base64Compressed = attachmentData?.base64Compressed ?? null; \r\n const base64WebpCompressed = attachmentData?.base64WebpCompressed ?? null; // the original compressed webp image base64\r\n\r\n const worker = new Worker(new URL(/* worker import */ __webpack_require__.p + __webpack_require__.u(\"assets_js_worker_js\"), __webpack_require__.b), {type: undefined}); // worker url\r\n const channel = new MessageChannel();\r\n worker.postMessage({\r\n action: 'compress',\r\n format,\r\n url,\r\n name,\r\n sourceType,\r\n outputType,\r\n mime,\r\n sizes,\r\n skipFull,\r\n timeout,\r\n isPreview,\r\n file,\r\n base64Compressed,\r\n base64WebpCompressed,\r\n type,\r\n options: this.options,\r\n //urlOriginal: urlOriginal,\r\n }, [channel.port2]);\r\n\r\n // Listen for compression requests from the worker via port1\r\n channel.port1.onmessage = async (ev) => {\r\n //console.log(\"Worker received compression request:\", ev.data);\r\n const { id, action, fileOrArrayBuffer, mime, options } = ev.data;\r\n if (action !== 'imageCompression') return;\r\n\r\n try {\r\n // Accept ArrayBuffer or Blob\r\n const blob = (fileOrArrayBuffer instanceof ArrayBuffer)\r\n ? new Blob([this.toUint8Array(fileOrArrayBuffer)], { type: mime })\r\n : fileOrArrayBuffer;\r\n\r\n const compressedBlob = await (0,browser_image_compression__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(blob, { useWebWorker: true, ...(options || {}) });\r\n\r\n // quick checks\r\n //console.log('compressedBlob.type, size:', compressedBlob.type, compressedBlob.size);\r\n // normalize final binary\r\n const compressedArrayBuffer = await compressedBlob.arrayBuffer();\r\n\r\n // Reply with the compressed ArrayBuffer (transfer to avoid copy)\r\n channel.port1.postMessage({ id, ok: true, arrayBuffer: compressedArrayBuffer }, [compressedArrayBuffer]);\r\n } catch (err) {\r\n channel.port1.postMessage({ id, ok: false, error: err.message || String(err) });\r\n }\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const timeoutId = setTimeout(() => {\r\n worker.terminate();\r\n console.warn('Worker compress terminated', name, sourceType, outputType);\r\n reject(__('Request timed out.', 'squeeze'));\r\n }, timeout);\r\n\r\n worker.onmessage = (event) => {\r\n clearTimeout(timeoutId);\r\n if (event.data.error) {\r\n reject(event.data.error);\r\n } else {\r\n resolve(event.data);\r\n }\r\n worker.terminate();\r\n };\r\n\r\n worker.onerror = (error) => {\r\n clearTimeout(timeoutId);\r\n reject(`Worker error: ${error.message}`);\r\n };\r\n });\r\n\r\n }\r\n\r\n handleUpload = async ({ attachment, base64, type = 'uncompressed', mediaIDs = [] }) => {\r\n\r\n const attachmentData = attachment.attributes;\r\n const url = attachmentData?.originalImageURL ?? attachmentData.url;\r\n const mime = attachmentData.mime;\r\n const filename = attachmentData?.originalImageName ?? attachmentData.filename;\r\n const attachmentID = attachmentData.id;\r\n const format = base64?.isDirectWebp && type !== 'path' ? 'webp' : mime.split(\"/\")[1];\r\n const sizes = attachmentData.sizes;\r\n\r\n const isDirectWebp = base64?.isDirectWebp && mime !== 'image/webp';\r\n const isBackupOriginal = this.options?.backup_original ?? false; // check if backup original is enabled\r\n let originalFile = attachmentData?.originalFile ?? null; // the original file, used for creating backup\r\n \r\n // if originalFile is not provided, try to get it from the URL\r\n // used when the attachment needs to be converted to webp from jpg or png\r\n if (!originalFile && type !== 'path' && isBackupOriginal && isDirectWebp) {\r\n const file = await (0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.getFileFromUrl)(url, filename, mime.split(\"/\")[1])\r\n //console.log('conveting original file to webp', file);\r\n originalFile = await this.convertFileToWebp(file);\r\n }\r\n //console.log('handleUpload attachmentData', attachmentData)\r\n\r\n const data = {\r\n action: 'squeeze_update_attachment',\r\n _ajax_nonce: this.nonce,\r\n filename: filename,\r\n type: 'image',\r\n format: format,\r\n base64: base64.base64,\r\n base64Sizes: base64.base64Sizes,\r\n base64Webp: base64.base64Webp,\r\n base64SizesWebp: base64.base64SizesWebp,\r\n attachmentID: attachmentID,\r\n url: url,\r\n process: type,\r\n originalFile: originalFile,\r\n }\r\n\r\n const formData = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.objectToFormData)(data);\r\n\r\n //console.log('squeeze_update_attachment', JSON.stringify(data, null, 2));\r\n\r\n try {\r\n const uploadResponse = await jQuery.ajax({\r\n url: this.ajaxUrl, // + '111',\r\n type: 'POST',\r\n data: formData,\r\n processData: false, // very important!\r\n contentType: false, // very important!\r\n });\r\n\r\n //if (uploadResponse.success) {\r\n uploadResponse['mediaIDs'] = mediaIDs;\r\n //}\r\n\r\n if (!uploadResponse?.data?.url) {\r\n uploadResponse.url = url; // fallback to original URL if not provided\r\n }\r\n\r\n //console.log('uploadResponse', JSON.stringify(uploadResponse, null, 2));\r\n\r\n return uploadResponse;\r\n \r\n } catch (error) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': error.message,\r\n 'success': false\r\n };\r\n }\r\n\r\n }\r\n\r\n handleBulkUpload = async (type = 'uncompressed', mediaIDs = []) => {\r\n let currentID;\r\n let attachment;\r\n\r\n switch (type) {\r\n case 'all':\r\n case 'uncompressed':\r\n currentID = mediaIDs[0];\r\n break;\r\n case 'path':\r\n currentID = mediaIDs[0]?.filename;\r\n break;\r\n default:\r\n currentID = 0;\r\n break;\r\n }\r\n\r\n if (type === 'path') {\r\n\r\n \r\n\r\n attachment = {\r\n attributes: {\r\n url: mediaIDs[0].url,\r\n mime: mediaIDs[0].mime,\r\n name: mediaIDs[0].name,\r\n filename: mediaIDs[0].filename,\r\n id: mediaIDs[0].id,\r\n sizes: mediaIDs[0]?.sizes,\r\n }\r\n }\r\n\r\n } else {\r\n\r\n const attachmentResponse = await this.getAttachment(currentID); \r\n if (attachmentResponse.success === false) {\r\n if (Array.isArray(mediaIDs)) mediaIDs.shift();\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': attachmentResponse.data\r\n }\r\n }\r\n const attachmentData = attachmentResponse.data;\r\n attachment = {\r\n attributes: {\r\n url: attachmentData.url,\r\n mime: attachmentData.mime,\r\n name: attachmentData.name,\r\n filename: attachmentData.filename,\r\n id: attachmentData.id,\r\n sizes: attachmentData.sizes,\r\n }\r\n }\r\n\r\n }\r\n\r\n if (Array.isArray(mediaIDs)) mediaIDs.shift();\r\n\r\n const mediaType = attachment.attributes.mime.split(\"/\")[0];\r\n const mediaSubType = attachment.attributes.mime.split(\"/\")[1];\r\n\r\n if (!(0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.maybeCompressAttachment)(mediaType, mediaSubType)) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': __('Skipped', 'squeeze')\r\n }\r\n }\r\n\r\n try {\r\n const compressData = await this.handleCompress( attachment, type );\r\n const uploadData = await this.handleUpload({ attachment: attachment, base64: compressData, type: type, mediaIDs: mediaIDs })\r\n\r\n return uploadData;\r\n\r\n } catch (error) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': error?.message ?? error,\r\n 'success': false\r\n }\r\n }\r\n\r\n }\r\n\r\n \r\n\r\n handleRestore = async (attachmentID) => {\r\n const data = {\r\n action: 'squeeze_restore_attachment',\r\n _ajax_nonce: this.nonce,\r\n attachmentID: attachmentID,\r\n };\r\n\r\n const response = await jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n };\r\n\r\n // Get list of attachments by path\r\n getAttachmentsByPath = async (path) => {\r\n\r\n const data = {\r\n action: 'squeeze_get_attachment_by_path',\r\n path: path,\r\n _ajax_nonce: this.nonce,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getAttachment = async (attachmentID) => {\r\n const data = {\r\n action: 'squeeze_get_attachment',\r\n _ajax_nonce: this.nonce,\r\n attachmentID: attachmentID,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getNextAttachments = async (page = 1, type = 'uncompressed', lastId = 0) => {\r\n const data = {\r\n action: 'squeeze_get_next_attachments',\r\n _ajax_nonce: this.nonce,\r\n page: page,\r\n type: type,\r\n lastId: lastId,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getDirectories = async (parentDir = false) => {\r\n const data = {\r\n action: 'squeeze_get_directories',\r\n _ajax_nonce: this.nonce,\r\n }\r\n\r\n if (parentDir) {\r\n data['parentDir'] = parentDir;\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n \r\n\r\n setOptions = async (options) => {\r\n const data = {\r\n action: 'squeeze_set_options',\r\n _ajax_nonce: this.nonce,\r\n options: options,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n /**\r\n * Compresses an image before upload.\r\n * If the compressSizes parameter is provided, it will compress the sizes specified along with the original image.\r\n * \r\n * @param {File} file - The file to be compressed\r\n * @param {object} compressSizes - Optional sizes for compression\r\n * @returns {Promise<object|boolean>} - Returns a Promise that resolves to the compressed file as a base64 object or false if the file is invalid\r\n */\r\n compressBeforeUpload = async (file, compressSizes = null) => {\r\n if (!(file instanceof Blob) && !(file instanceof File)) {\r\n console.error('Invalid file type:', file);\r\n return false; // Return false if the file is not a Blob or File\r\n }\r\n\r\n const attachment = {\r\n attributes: {\r\n url: null,\r\n mime: file.type,\r\n name: file.name,\r\n filename: file.name,\r\n id: 0,\r\n sizes: compressSizes,\r\n file: file,\r\n skipFull: false,\r\n }\r\n }\r\n\r\n try {\r\n const base64Obj = await this.handleCompress(attachment);\r\n\r\n return base64Obj; // Return the compressed file as base64Obj\r\n } catch (error) {\r\n console.error(error);\r\n return error; // Return the error for further handling\r\n }\r\n }\r\n\r\n isWebpEncodingSupported = async () => {\r\n try {\r\n const canvas = document.createElement('canvas');\r\n if (!canvas.toDataURL) return false;\r\n const data = canvas.toDataURL('image/webp');\r\n // WebP signature starts with \"data:image/webp\"\r\n return data.indexOf('data:image/webp') === 0;\r\n } catch (e) {\r\n return false;\r\n }\r\n }\r\n\r\n handleConvertFileToWebp = async (args) => {\r\n const timeout = this.timeout;\r\n \r\n const { file, sourceType, name } = args;\r\n const fileBuffer = await file.arrayBuffer();\r\n const mime = sourceType === 'png' ? 'image/png' : 'image/jpeg';\r\n const inputBlob = new Blob([fileBuffer], { type: mime });\r\n // compression options you can tune\r\n const imageCompressionOptions = {\r\n useWebWorker: true,\r\n fileType: 'image/webp',\r\n };\r\n\r\n return new Promise(async (resolve, reject) => {\r\n const timeoutId = setTimeout(() => {\r\n console.warn('Worker convertToWebp terminated', name);\r\n reject(new Error(__('Request timed out.', 'squeeze')));\r\n }, timeout);\r\n\r\n try {\r\n\r\n const compressedBlob = await (0,browser_image_compression__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(inputBlob, imageCompressionOptions);\r\n\r\n // quick checks\r\n //console.log('compressedBlob.type, size:', compressedBlob.type, compressedBlob.size);\r\n // normalize final binary\r\n const webpArrayBuffer = await compressedBlob.arrayBuffer();\r\n\r\n const webpName = name.replace(/\\.\\w+$/, '.webp');\r\n const webpFile = new File([webpArrayBuffer], webpName, { type: 'image/webp', lastModified: Date.now() });\r\n\r\n clearTimeout(timeoutId);\r\n resolve({ webpFile }); // must return object with webpFile prop\r\n } catch (error) {\r\n clearTimeout(timeoutId);\r\n console.error('Error during image processing:', error);\r\n reject(error);\r\n }\r\n });\r\n \r\n }\r\n\r\n // helper: normalize buffer-like inputs to Uint8Array\r\n toUint8Array = (bufferLike) => {\r\n if (bufferLike instanceof Uint8Array) return bufferLike;\r\n if (bufferLike instanceof ArrayBuffer) return new Uint8Array(bufferLike);\r\n if (ArrayBuffer.isView(bufferLike)) return new Uint8Array(bufferLike.buffer, bufferLike.byteOffset, bufferLike.byteLength);\r\n // Node Buffer (in some environments) has .buffer and .byteOffset\r\n if (bufferLike && typeof bufferLike === 'object' && bufferLike.buffer) {\r\n return new Uint8Array(bufferLike.buffer, bufferLike.byteOffset || 0, bufferLike.byteLength || bufferLike.length);\r\n }\r\n throw new Error('Unsupported buffer type: ' + Object.prototype.toString.call(bufferLike));\r\n }\r\n\r\n /**\r\n * Convert an image File to WebP.\r\n *\r\n * @param {File} inputFile — the original image File (e.g. JPEG, PNG).\r\n * @returns {Promise<File>} — a Promise that resolves to a WebP File.\r\n */\r\n convertFileToWebp = async (inputFile) => {\r\n // Ensure it’s an image\r\n if (!inputFile.type.startsWith('image/')) {\r\n throw new Error('Input must be an image File');\r\n }\r\n if (this.isCanvasExtractionBlocked()) {\r\n console.warn('Canvas extraction is blocked');\r\n // Handle the case where canvas extraction is blocked\r\n alert(__('Image conversion blocked by browser privacy setting', 'squeeze'));\r\n throw new Error('Canvas extraction is blocked');\r\n } else {\r\n console.log('Canvas extraction is allowed');\r\n }\r\n const args = {\r\n file: inputFile,\r\n sourceType: inputFile.type.split('/')[1], // e.g. 'jpeg', 'png'\r\n name: inputFile.name,\r\n }\r\n const webpSupported = await this.isWebpEncodingSupported();\r\n try {\r\n // Convert the image to WebP\r\n let webpData;\r\n if (webpSupported) {\r\n //console.log('WebP decoding is supported');\r\n webpData = await this.handleConvertFileToWebp(args);\r\n } else {\r\n console.warn('WebP encoding not supported on this device. Using original format instead.');\r\n const webpName = args.name.replace(/\\.\\w+$/, '.webp');\r\n webpData = await this.convertToWebpWithJsquash(inputFile, webpName);\r\n }\r\n if (!webpData || !webpData.webpFile) {\r\n throw new Error('Failed to convert image to WebP');\r\n }\r\n\r\n return webpData.webpFile; // Return the WebP File\r\n\r\n } catch (error) {\r\n console.error('Error converting image to WebP:', error);\r\n throw error; // Re-throw the error for further handling\r\n }\r\n\r\n }\r\n\r\n /**\r\n * Convert an input (File | Blob | ArrayBuffer | Uint8Array) to a WebP File using @jsquash/webp.\r\n * @param {File|Blob|ArrayBuffer|Uint8Array} input\r\n * @param {string} outputName - desired filename for the .webp output\r\n * @param {{quality?: number, lossless?: boolean, method?: number}} [opts] - encoder options (quality 0-100)\r\n * @returns {Promise<File>} - WebP File\r\n */\r\n convertToWebpWithJsquash = async (input, outputName = 'out.webp', opts = { quality: 100 }) => {\r\n // Normalize input to Blob\r\n let blob;\r\n if (input instanceof Blob) {\r\n blob = input;\r\n } else if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {\r\n blob = new Blob([input]);\r\n } else {\r\n throw new Error('Unsupported input type. Provide File, Blob, ArrayBuffer or TypedArray.');\r\n }\r\n\r\n // Load image into <img>\r\n const dataURL = await new Promise((resolve, reject) => {\r\n const fr = new FileReader();\r\n fr.onload = () => resolve(fr.result);\r\n fr.onerror = reject;\r\n fr.readAsDataURL(blob);\r\n });\r\n\r\n const img = await new Promise((resolve, reject) => {\r\n const i = new Image();\r\n i.onload = () => resolve(i);\r\n i.onerror = () => reject(new Error('Failed to load image for conversion'));\r\n i.src = dataURL;\r\n // avoid cross-origin taint if data-url is used\r\n });\r\n\r\n // Draw to canvas and extract ImageData\r\n const canvas = document.createElement('canvas');\r\n canvas.width = img.naturalWidth || img.width;\r\n canvas.height = img.naturalHeight || img.height;\r\n const ctx = canvas.getContext('2d');\r\n ctx.drawImage(img, 0, 0);\r\n const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\r\n\r\n // encode -> returns ArrayBuffer (WASM libwebp)\r\n // opts example: { quality: 75, lossless: false, method: 4 }\r\n const webpArrayBuffer = await (0,_jsquash_webp__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(imageData, opts);\r\n\r\n // Validate a little (size)\r\n if (!webpArrayBuffer || webpArrayBuffer.byteLength < 20) {\r\n throw new Error('jsquash produced an invalid WebP buffer');\r\n }\r\n\r\n // Wrap into File\r\n const webpFile = new File([webpArrayBuffer], outputName.replace(/\\.\\w+$/, '.webp'), {\r\n type: 'image/webp',\r\n lastModified: Date.now(),\r\n });\r\n\r\n return {webpFile};\r\n }\r\n\r\n isCanvasExtractionBlocked = () => {\r\n try {\r\n const c = document.createElement('canvas');\r\n c.width = 2;\r\n c.height = 2;\r\n const ctx = c.getContext('2d');\r\n if (!ctx) return false; // no ctx => treat as not-blocked here (or handle separately)\r\n\r\n // draw four known opaque pixels (no alpha surprise)\r\n ctx.fillStyle = 'rgb(255,0,0)'; // top-left\r\n ctx.fillRect(0, 0, 1, 1);\r\n\r\n ctx.fillStyle = 'rgb(0,255,0)'; // top-right\r\n ctx.fillRect(1, 0, 1, 1);\r\n\r\n ctx.fillStyle = 'rgb(0,0,255)'; // bottom-left\r\n ctx.fillRect(0, 1, 1, 1);\r\n\r\n ctx.fillStyle = 'rgb(1,2,3)'; // bottom-right (small values detect naive normalization)\r\n ctx.fillRect(1, 1, 1, 1);\r\n\r\n let data;\r\n try {\r\n data = ctx.getImageData(0, 0, 2, 2).data; // Uint8ClampedArray length 16 (4*2*2)\r\n } catch (err) {\r\n // thrown security/blocked error (Firefox/resistFingerprinting sometimes throws)\r\n if (err && (err.name === 'SecurityError' || /blocked/i.test(err.message))) return true;\r\n // other errors: be conservative and return false\r\n return false;\r\n }\r\n\r\n // Expected RGBA order for pixels (top-left, top-right, bottom-left, bottom-right)\r\n const expected = new Uint8ClampedArray([\r\n 255, 0, 0, 255, // top-left\r\n 0, 255, 0, 255, // top-right\r\n 0, 0, 255, 255, // bottom-left\r\n 1, 2, 3, 255 // bottom-right\r\n ]);\r\n\r\n // Compare each channel exactly. If ANY mismatch — canvas data was altered.\r\n for (let i = 0; i < expected.length; i++) {\r\n if (data[i] !== expected[i]) {\r\n // mismatch -> either fingerprint-protection normalized the canvas OR something else tainted it\r\n return true;\r\n }\r\n }\r\n\r\n // No thrown error and pixels match exactly => extraction works\r\n return false;\r\n } catch (e) {\r\n // If something unexpected goes wrong, assume not blocked so caller can handle fallback.\r\n return false;\r\n }\r\n }\r\n\r\n\r\n}\n\n//# sourceURL=webpack:///./assets/js/squeeze.js?"); 50 50 51 51 /***/ }), -
squeeze/trunk/assets/js/script.bundle.js
r3345284 r3346520 47 47 /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 48 48 49 eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ SQUEEZE)\n/* harmony export */ });\n/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./helpers.js */ \"./assets/js/helpers.js\");\n/* harmony import */ var browser_image_compression__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! browser-image-compression */ \"./node_modules/browser-image-compression/dist/browser-image-compression.mjs\");\n/* harmony import */ var _jsquash_webp__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @jsquash/webp */ \"./node_modules/@jsquash/webp/encode.js\");\n\r\n\r\n\r\n\r\n\r\n\r\nconst { __ } = wp.i18n; // Import __() from wp.i18n\r\n\r\nclass SQUEEZE {\r\n\r\n constructor(squeeze) {\r\n this.options = JSON.parse(squeeze.options); // plugin options\r\n this.nonce = squeeze.nonce; // nonce\r\n this.ajaxUrl = squeeze.ajaxUrl; // ajax url\r\n this.timeout = parseInt(this.options.timeout) * 1000; // convert to milliseconds\r\n this.poolSize = navigator.hardwareConcurrency || 1; // number of threads\r\n }\r\n\r\n handleCompress = async ( attachment, type = 'uncompressed' ) => {\r\n const attachmentData = attachment.attributes;\r\n const url = attachmentData?.originalImageURL ?? attachmentData.url;\r\n const mime = attachmentData.mime;\r\n const name = attachmentData.name;\r\n const filename = attachmentData?.originalImageName ?? attachmentData.filename;\r\n const attachmentID = attachmentData.id;\r\n const sizes = attachmentData.sizes;\r\n const format = mime.split(\"/\")[1];\r\n const sourceType = format;\r\n const outputType = format;\r\n const skipFull = attachmentData?.skipFull ?? attachmentData.originalImageName === undefined ? true : false;\r\n const timeout = this.timeout;\r\n const isPreview = attachmentData?.isPreview ?? false;\r\n const file = attachmentData?.file ?? null; // the original file, it passed for the newly uploaded attachment to compress it\r\n\r\n // the original compressed image base64, it passed when the attachment was already compressed and we need to compress thumbs only\r\n // we pass it in order to prevent re-compressing the original image when we need to compress thumbs only\r\n const base64Compressed = attachmentData?.base64Compressed ?? null; \r\n const base64WebpCompressed = attachmentData?.base64WebpCompressed ?? null; // the original compressed webp image base64\r\n\r\n const worker = new Worker(new URL(/* worker import */ __webpack_require__.p + __webpack_require__.u(\"assets_js_worker_js\"), __webpack_require__.b), {type: undefined}); // worker url\r\n const channel = new MessageChannel();\r\n worker.postMessage({\r\n action: 'compress',\r\n format,\r\n url,\r\n name,\r\n sourceType,\r\n outputType,\r\n mime,\r\n sizes,\r\n skipFull,\r\n timeout,\r\n isPreview,\r\n file,\r\n base64Compressed,\r\n base64WebpCompressed,\r\n type,\r\n options: this.options,\r\n //urlOriginal: urlOriginal,\r\n }, [channel.port2]);\r\n\r\n // Listen for compression requests from the worker via port1\r\n channel.port1.onmessage = async (ev) => {\r\n //console.log(\"Worker received compression request:\", ev.data);\r\n const { id, action, fileOrArrayBuffer, mime, options } = ev.data;\r\n if (action !== 'imageCompression') return;\r\n\r\n try {\r\n // Accept ArrayBuffer or Blob\r\n const blob = (fileOrArrayBuffer instanceof ArrayBuffer)\r\n ? new Blob([this.toUint8Array(fileOrArrayBuffer)], { type: mime })\r\n : fileOrArrayBuffer;\r\n\r\n const compressedBlob = await (0,browser_image_compression__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(blob, { useWebWorker: true, ...(options || {}) });\r\n\r\n // quick checks\r\n //console.log('compressedBlob.type, size:', compressedBlob.type, compressedBlob.size);\r\n // normalize final binary\r\n const compressedArrayBuffer = await compressedBlob.arrayBuffer();\r\n\r\n // Reply with the compressed ArrayBuffer (transfer to avoid copy)\r\n channel.port1.postMessage({ id, ok: true, arrayBuffer: compressedArrayBuffer }, [compressedArrayBuffer]);\r\n } catch (err) {\r\n channel.port1.postMessage({ id, ok: false, error: err.message || String(err) });\r\n }\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const timeoutId = setTimeout(() => {\r\n worker.terminate();\r\n console.warn('Worker compress terminated', name, sourceType, outputType);\r\n reject(__('Request timed out.', 'squeeze'));\r\n }, timeout);\r\n\r\n worker.onmessage = (event) => {\r\n clearTimeout(timeoutId);\r\n if (event.data.error) {\r\n reject(event.data.error);\r\n } else {\r\n resolve(event.data);\r\n }\r\n worker.terminate();\r\n };\r\n\r\n worker.onerror = (error) => {\r\n clearTimeout(timeoutId);\r\n reject(`Worker error: ${error.message}`);\r\n };\r\n });\r\n\r\n }\r\n\r\n handleUpload = async ({ attachment, base64, type = 'uncompressed', mediaIDs = [] }) => {\r\n\r\n const attachmentData = attachment.attributes;\r\n const url = attachmentData?.originalImageURL ?? attachmentData.url;\r\n const mime = attachmentData.mime;\r\n const filename = attachmentData?.originalImageName ?? attachmentData.filename;\r\n const attachmentID = attachmentData.id;\r\n const format = base64?.isDirectWebp && type !== 'path' ? 'webp' : mime.split(\"/\")[1];\r\n const sizes = attachmentData.sizes;\r\n\r\n const isDirectWebp = base64?.isDirectWebp && mime !== 'image/webp';\r\n const isBackupOriginal = this.options?.backup_original ?? false; // check if backup original is enabled\r\n let originalFile = attachmentData?.originalFile ?? null; // the original file, used for creating backup\r\n \r\n // if originalFile is not provided, try to get it from the URL\r\n // used when the attachment needs to be converted to webp from jpg or png\r\n if (!originalFile && type !== 'path' && isBackupOriginal && isDirectWebp) {\r\n const file = await (0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.getFileFromUrl)(url, filename, mime.split(\"/\")[1])\r\n //console.log('conveting original file to webp', file);\r\n originalFile = await this.convertFileToWebp(file);\r\n }\r\n //console.log('handleUpload attachmentData', attachmentData)\r\n\r\n const data = {\r\n action: 'squeeze_update_attachment',\r\n _ajax_nonce: this.nonce,\r\n filename: filename,\r\n type: 'image',\r\n format: format,\r\n base64: base64.base64,\r\n base64Sizes: base64.base64Sizes,\r\n base64Webp: base64.base64Webp,\r\n base64SizesWebp: base64.base64SizesWebp,\r\n attachmentID: attachmentID,\r\n url: url,\r\n process: type,\r\n originalFile: originalFile,\r\n }\r\n\r\n const formData = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.objectToFormData)(data);\r\n\r\n //console.log('squeeze_update_attachment', JSON.stringify(data, null, 2));\r\n\r\n try {\r\n const uploadResponse = await jQuery.ajax({\r\n url: this.ajaxUrl, // + '111',\r\n type: 'POST',\r\n data: formData,\r\n processData: false, // very important!\r\n contentType: false, // very important!\r\n });\r\n\r\n //if (uploadResponse.success) {\r\n uploadResponse['mediaIDs'] = mediaIDs;\r\n //}\r\n\r\n if (!uploadResponse?.data?.url) {\r\n uploadResponse.url = url; // fallback to original URL if not provided\r\n }\r\n\r\n //console.log('uploadResponse', JSON.stringify(uploadResponse, null, 2));\r\n\r\n return uploadResponse;\r\n \r\n } catch (error) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': error.message,\r\n 'success': false\r\n };\r\n }\r\n\r\n }\r\n\r\n handleBulkUpload = async (type = 'uncompressed', mediaIDs = []) => {\r\n let currentID;\r\n let attachment;\r\n\r\n switch (type) {\r\n case 'all':\r\n case 'uncompressed':\r\n currentID = mediaIDs[0];\r\n break;\r\n case 'path':\r\n currentID = mediaIDs[0]?.filename;\r\n break;\r\n default:\r\n currentID = 0;\r\n break;\r\n }\r\n\r\n if (type === 'path') {\r\n\r\n \r\n\r\n attachment = {\r\n attributes: {\r\n url: mediaIDs[0].url,\r\n mime: mediaIDs[0].mime,\r\n name: mediaIDs[0].name,\r\n filename: mediaIDs[0].filename,\r\n id: mediaIDs[0].id,\r\n sizes: mediaIDs[0]?.sizes,\r\n }\r\n }\r\n\r\n } else {\r\n\r\n const attachmentResponse = await this.getAttachment(currentID); \r\n if (attachmentResponse.success === false) {\r\n if (Array.isArray(mediaIDs)) mediaIDs.shift();\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': attachmentResponse.data\r\n }\r\n }\r\n const attachmentData = attachmentResponse.data;\r\n attachment = {\r\n attributes: {\r\n url: attachmentData.url,\r\n mime: attachmentData.mime,\r\n name: attachmentData.name,\r\n filename: attachmentData.filename,\r\n id: attachmentData.id,\r\n sizes: attachmentData.sizes,\r\n }\r\n }\r\n\r\n }\r\n\r\n if (Array.isArray(mediaIDs)) mediaIDs.shift();\r\n\r\n const mediaType = attachment.attributes.mime.split(\"/\")[0];\r\n const mediaSubType = attachment.attributes.mime.split(\"/\")[1];\r\n\r\n if (!(0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.maybeCompressAttachment)(mediaType, mediaSubType)) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': __('Skipped', 'squeeze')\r\n }\r\n }\r\n\r\n try {\r\n const compressData = await this.handleCompress( attachment, type );\r\n const uploadData = await this.handleUpload({ attachment: attachment, base64: compressData, type: type, mediaIDs: mediaIDs })\r\n\r\n return uploadData;\r\n\r\n } catch (error) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': error?.message ?? error,\r\n 'success': false\r\n }\r\n }\r\n\r\n }\r\n\r\n \r\n\r\n handleRestore = async (attachmentID) => {\r\n const data = {\r\n action: 'squeeze_restore_attachment',\r\n _ajax_nonce: this.nonce,\r\n attachmentID: attachmentID,\r\n };\r\n\r\n const response = await jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n };\r\n\r\n // Get list of attachments by path\r\n getAttachmentsByPath = async (path) => {\r\n\r\n const data = {\r\n action: 'squeeze_get_attachment_by_path',\r\n path: path,\r\n _ajax_nonce: this.nonce,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getAttachment = async (attachmentID) => {\r\n const data = {\r\n action: 'squeeze_get_attachment',\r\n _ajax_nonce: this.nonce,\r\n attachmentID: attachmentID,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getNextAttachments = async (page = 1, type = 'uncompressed', lastId = 0) => {\r\n const data = {\r\n action: 'squeeze_get_next_attachments',\r\n _ajax_nonce: this.nonce,\r\n page: page,\r\n type: type,\r\n lastId: lastId,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getDirectories = async (parentDir = false) => {\r\n const data = {\r\n action: 'squeeze_get_directories',\r\n _ajax_nonce: this.nonce,\r\n }\r\n\r\n if (parentDir) {\r\n data['parentDir'] = parentDir;\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n \r\n\r\n setOptions = async (options) => {\r\n const data = {\r\n action: 'squeeze_set_options',\r\n _ajax_nonce: this.nonce,\r\n options: options,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n /**\r\n * Compresses an image before upload.\r\n * If the compressSizes parameter is provided, it will compress the sizes specified along with the original image.\r\n * \r\n * @param {File} file - The file to be compressed\r\n * @param {object} compressSizes - Optional sizes for compression\r\n * @returns {Promise<object|boolean>} - Returns a Promise that resolves to the compressed file as a base64 object or false if the file is invalid\r\n */\r\n compressBeforeUpload = async (file, compressSizes = null) => {\r\n if (!(file instanceof Blob) && !(file instanceof File)) {\r\n console.error('Invalid file type:', file);\r\n return false; // Return false if the file is not a Blob or File\r\n }\r\n\r\n const attachment = {\r\n attributes: {\r\n url: null,\r\n mime: file.type,\r\n name: file.name,\r\n filename: file.name,\r\n id: 0,\r\n sizes: compressSizes,\r\n file: file,\r\n skipFull: false,\r\n }\r\n }\r\n\r\n try {\r\n const base64Obj = await this.handleCompress(attachment);\r\n\r\n return base64Obj; // Return the compressed file as base64Obj\r\n } catch (error) {\r\n console.error(error);\r\n return error; // Return the error for further handling\r\n }\r\n }\r\n\r\n isWebpEncodingSupported = async () => {\r\n try {\r\n const canvas = document.createElement('canvas');\r\n if (!canvas.toDataURL) return false;\r\n const data = canvas.toDataURL('image/webp');\r\n // WebP signature starts with \"data:image/webp\"\r\n return data.indexOf('data:image/webp') === 0;\r\n } catch (e) {\r\n return false;\r\n }\r\n }\r\n\r\n handleConvertFileToWebp = async (args) => {\r\n const timeout = this.timeout;\r\n \r\n const { file, sourceType, name } = args;\r\n const fileBuffer = await file.arrayBuffer();\r\n const mime = sourceType === 'png' ? 'image/png' : 'image/jpeg';\r\n const inputBlob = new Blob([fileBuffer], { type: mime });\r\n // compression options you can tune\r\n const imageCompressionOptions = {\r\n useWebWorker: true,\r\n fileType: 'image/webp',\r\n };\r\n\r\n return new Promise(async (resolve, reject) => {\r\n const timeoutId = setTimeout(() => {\r\n console.warn('Worker convertToWebp terminated', name);\r\n reject(new Error(__('Request timed out.', 'squeeze')));\r\n }, timeout);\r\n\r\n try {\r\n\r\n const compressedBlob = await (0,browser_image_compression__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(inputBlob, imageCompressionOptions);\r\n\r\n // quick checks\r\n //console.log('compressedBlob.type, size:', compressedBlob.type, compressedBlob.size);\r\n // normalize final binary\r\n const webpArrayBuffer = await compressedBlob.arrayBuffer();\r\n\r\n const webpName = name.replace(/\\.\\w+$/, '.webp');\r\n const webpFile = new File([webpArrayBuffer], webpName, { type: 'image/webp', lastModified: Date.now() });\r\n\r\n clearTimeout(timeoutId);\r\n resolve({ webpFile }); // must return object with webpFile prop\r\n } catch (error) {\r\n clearTimeout(timeoutId);\r\n console.error('Error during image processing:', error);\r\n reject(error);\r\n }\r\n });\r\n \r\n }\r\n\r\n // helper: normalize buffer-like inputs to Uint8Array\r\n toUint8Array = (bufferLike) => {\r\n if (bufferLike instanceof Uint8Array) return bufferLike;\r\n if (bufferLike instanceof ArrayBuffer) return new Uint8Array(bufferLike);\r\n if (ArrayBuffer.isView(bufferLike)) return new Uint8Array(bufferLike.buffer, bufferLike.byteOffset, bufferLike.byteLength);\r\n // Node Buffer (in some environments) has .buffer and .byteOffset\r\n if (bufferLike && typeof bufferLike === 'object' && bufferLike.buffer) {\r\n return new Uint8Array(bufferLike.buffer, bufferLike.byteOffset || 0, bufferLike.byteLength || bufferLike.length);\r\n }\r\n throw new Error('Unsupported buffer type: ' + Object.prototype.toString.call(bufferLike));\r\n }\r\n\r\n /**\r\n * Convert an image File to WebP.\r\n *\r\n * @param {File} inputFile — the original image File (e.g. JPEG, PNG).\r\n * @returns {Promise<File>} — a Promise that resolves to a WebP File.\r\n */\r\n convertFileToWebp = async (inputFile) => {\r\n // Ensure it’s an image\r\n if (!inputFile.type.startsWith('image/')) {\r\n throw new Error('Input must be an image File');\r\n }\r\n const args = {\r\n file: inputFile,\r\n sourceType: inputFile.type.split('/')[1], // e.g. 'jpeg', 'png'\r\n name: inputFile.name,\r\n }\r\n const webpSupported = await this.isWebpEncodingSupported();\r\n try {\r\n // Convert the image to WebP\r\n let webpData;\r\n if (webpSupported) {\r\n webpData = await this.handleConvertFileToWebp(args);\r\n } else {\r\n console.warn('WebP encoding not supported on this device. Using original format instead.');\r\n const webpName = args.name.replace(/\\.\\w+$/, '.webp');\r\n webpData = await this.convertToWebpWithJsquash(inputFile, webpName);\r\n }\r\n if (!webpData || !webpData.webpFile) {\r\n throw new Error('Failed to convert image to WebP');\r\n }\r\n\r\n return webpData.webpFile; // Return the WebP File\r\n\r\n } catch (error) {\r\n console.error('Error converting image to WebP:', error);\r\n throw error; // Re-throw the error for further handling\r\n }\r\n\r\n }\r\n\r\n /**\r\n * Convert an input (File | Blob | ArrayBuffer | Uint8Array) to a WebP File using @jsquash/webp.\r\n * @param {File|Blob|ArrayBuffer|Uint8Array} input\r\n * @param {string} outputName - desired filename for the .webp output\r\n * @param {{quality?: number, lossless?: boolean, method?: number}} [opts] - encoder options (quality 0-100)\r\n * @returns {Promise<File>} - WebP File\r\n */\r\n convertToWebpWithJsquash = async (input, outputName = 'out.webp', opts = { quality: 100 }) => {\r\n // Normalize input to Blob\r\n let blob;\r\n if (input instanceof Blob) {\r\n blob = input;\r\n } else if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {\r\n blob = new Blob([input]);\r\n } else {\r\n throw new Error('Unsupported input type. Provide File, Blob, ArrayBuffer or TypedArray.');\r\n }\r\n\r\n // Load image into <img>\r\n const dataURL = await new Promise((resolve, reject) => {\r\n const fr = new FileReader();\r\n fr.onload = () => resolve(fr.result);\r\n fr.onerror = reject;\r\n fr.readAsDataURL(blob);\r\n });\r\n\r\n const img = await new Promise((resolve, reject) => {\r\n const i = new Image();\r\n i.onload = () => resolve(i);\r\n i.onerror = () => reject(new Error('Failed to load image for conversion'));\r\n i.src = dataURL;\r\n // avoid cross-origin taint if data-url is used\r\n });\r\n\r\n // Draw to canvas and extract ImageData\r\n const canvas = document.createElement('canvas');\r\n canvas.width = img.naturalWidth || img.width;\r\n canvas.height = img.naturalHeight || img.height;\r\n const ctx = canvas.getContext('2d');\r\n ctx.drawImage(img, 0, 0);\r\n const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\r\n\r\n // encode -> returns ArrayBuffer (WASM libwebp)\r\n // opts example: { quality: 75, lossless: false, method: 4 }\r\n const webpArrayBuffer = await (0,_jsquash_webp__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(imageData, opts);\r\n\r\n // Validate a little (size)\r\n if (!webpArrayBuffer || webpArrayBuffer.byteLength < 20) {\r\n throw new Error('jsquash produced an invalid WebP buffer');\r\n }\r\n\r\n // Wrap into File\r\n const webpFile = new File([webpArrayBuffer], outputName.replace(/\\.\\w+$/, '.webp'), {\r\n type: 'image/webp',\r\n lastModified: Date.now(),\r\n });\r\n\r\n return {webpFile};\r\n }\r\n\r\n}\n\n//# sourceURL=webpack:///./assets/js/squeeze.js?");49 eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ SQUEEZE)\n/* harmony export */ });\n/* harmony import */ var _helpers_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./helpers.js */ \"./assets/js/helpers.js\");\n/* harmony import */ var browser_image_compression__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! browser-image-compression */ \"./node_modules/browser-image-compression/dist/browser-image-compression.mjs\");\n/* harmony import */ var _jsquash_webp__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! @jsquash/webp */ \"./node_modules/@jsquash/webp/encode.js\");\n\r\n\r\n\r\n\r\n\r\n\r\nconst { __ } = wp.i18n; // Import __() from wp.i18n\r\n\r\nclass SQUEEZE {\r\n\r\n constructor(squeeze) {\r\n this.options = JSON.parse(squeeze.options); // plugin options\r\n this.nonce = squeeze.nonce; // nonce\r\n this.ajaxUrl = squeeze.ajaxUrl; // ajax url\r\n this.timeout = parseInt(this.options.timeout) * 1000; // convert to milliseconds\r\n this.poolSize = navigator.hardwareConcurrency || 1; // number of threads\r\n }\r\n\r\n handleCompress = async ( attachment, type = 'uncompressed' ) => {\r\n const attachmentData = attachment.attributes;\r\n const url = attachmentData?.originalImageURL ?? attachmentData.url;\r\n const mime = attachmentData.mime;\r\n const name = attachmentData.name;\r\n const filename = attachmentData?.originalImageName ?? attachmentData.filename;\r\n const attachmentID = attachmentData.id;\r\n const sizes = attachmentData.sizes;\r\n const format = mime.split(\"/\")[1];\r\n const sourceType = format;\r\n const outputType = format;\r\n const skipFull = attachmentData?.skipFull ?? attachmentData.originalImageName === undefined ? true : false;\r\n const timeout = this.timeout;\r\n const isPreview = attachmentData?.isPreview ?? false;\r\n const file = attachmentData?.file ?? null; // the original file, it passed for the newly uploaded attachment to compress it\r\n\r\n // the original compressed image base64, it passed when the attachment was already compressed and we need to compress thumbs only\r\n // we pass it in order to prevent re-compressing the original image when we need to compress thumbs only\r\n const base64Compressed = attachmentData?.base64Compressed ?? null; \r\n const base64WebpCompressed = attachmentData?.base64WebpCompressed ?? null; // the original compressed webp image base64\r\n\r\n const worker = new Worker(new URL(/* worker import */ __webpack_require__.p + __webpack_require__.u(\"assets_js_worker_js\"), __webpack_require__.b), {type: undefined}); // worker url\r\n const channel = new MessageChannel();\r\n worker.postMessage({\r\n action: 'compress',\r\n format,\r\n url,\r\n name,\r\n sourceType,\r\n outputType,\r\n mime,\r\n sizes,\r\n skipFull,\r\n timeout,\r\n isPreview,\r\n file,\r\n base64Compressed,\r\n base64WebpCompressed,\r\n type,\r\n options: this.options,\r\n //urlOriginal: urlOriginal,\r\n }, [channel.port2]);\r\n\r\n // Listen for compression requests from the worker via port1\r\n channel.port1.onmessage = async (ev) => {\r\n //console.log(\"Worker received compression request:\", ev.data);\r\n const { id, action, fileOrArrayBuffer, mime, options } = ev.data;\r\n if (action !== 'imageCompression') return;\r\n\r\n try {\r\n // Accept ArrayBuffer or Blob\r\n const blob = (fileOrArrayBuffer instanceof ArrayBuffer)\r\n ? new Blob([this.toUint8Array(fileOrArrayBuffer)], { type: mime })\r\n : fileOrArrayBuffer;\r\n\r\n const compressedBlob = await (0,browser_image_compression__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(blob, { useWebWorker: true, ...(options || {}) });\r\n\r\n // quick checks\r\n //console.log('compressedBlob.type, size:', compressedBlob.type, compressedBlob.size);\r\n // normalize final binary\r\n const compressedArrayBuffer = await compressedBlob.arrayBuffer();\r\n\r\n // Reply with the compressed ArrayBuffer (transfer to avoid copy)\r\n channel.port1.postMessage({ id, ok: true, arrayBuffer: compressedArrayBuffer }, [compressedArrayBuffer]);\r\n } catch (err) {\r\n channel.port1.postMessage({ id, ok: false, error: err.message || String(err) });\r\n }\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const timeoutId = setTimeout(() => {\r\n worker.terminate();\r\n console.warn('Worker compress terminated', name, sourceType, outputType);\r\n reject(__('Request timed out.', 'squeeze'));\r\n }, timeout);\r\n\r\n worker.onmessage = (event) => {\r\n clearTimeout(timeoutId);\r\n if (event.data.error) {\r\n reject(event.data.error);\r\n } else {\r\n resolve(event.data);\r\n }\r\n worker.terminate();\r\n };\r\n\r\n worker.onerror = (error) => {\r\n clearTimeout(timeoutId);\r\n reject(`Worker error: ${error.message}`);\r\n };\r\n });\r\n\r\n }\r\n\r\n handleUpload = async ({ attachment, base64, type = 'uncompressed', mediaIDs = [] }) => {\r\n\r\n const attachmentData = attachment.attributes;\r\n const url = attachmentData?.originalImageURL ?? attachmentData.url;\r\n const mime = attachmentData.mime;\r\n const filename = attachmentData?.originalImageName ?? attachmentData.filename;\r\n const attachmentID = attachmentData.id;\r\n const format = base64?.isDirectWebp && type !== 'path' ? 'webp' : mime.split(\"/\")[1];\r\n const sizes = attachmentData.sizes;\r\n\r\n const isDirectWebp = base64?.isDirectWebp && mime !== 'image/webp';\r\n const isBackupOriginal = this.options?.backup_original ?? false; // check if backup original is enabled\r\n let originalFile = attachmentData?.originalFile ?? null; // the original file, used for creating backup\r\n \r\n // if originalFile is not provided, try to get it from the URL\r\n // used when the attachment needs to be converted to webp from jpg or png\r\n if (!originalFile && type !== 'path' && isBackupOriginal && isDirectWebp) {\r\n const file = await (0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.getFileFromUrl)(url, filename, mime.split(\"/\")[1])\r\n //console.log('conveting original file to webp', file);\r\n originalFile = await this.convertFileToWebp(file);\r\n }\r\n //console.log('handleUpload attachmentData', attachmentData)\r\n\r\n const data = {\r\n action: 'squeeze_update_attachment',\r\n _ajax_nonce: this.nonce,\r\n filename: filename,\r\n type: 'image',\r\n format: format,\r\n base64: base64.base64,\r\n base64Sizes: base64.base64Sizes,\r\n base64Webp: base64.base64Webp,\r\n base64SizesWebp: base64.base64SizesWebp,\r\n attachmentID: attachmentID,\r\n url: url,\r\n process: type,\r\n originalFile: originalFile,\r\n }\r\n\r\n const formData = (0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.objectToFormData)(data);\r\n\r\n //console.log('squeeze_update_attachment', JSON.stringify(data, null, 2));\r\n\r\n try {\r\n const uploadResponse = await jQuery.ajax({\r\n url: this.ajaxUrl, // + '111',\r\n type: 'POST',\r\n data: formData,\r\n processData: false, // very important!\r\n contentType: false, // very important!\r\n });\r\n\r\n //if (uploadResponse.success) {\r\n uploadResponse['mediaIDs'] = mediaIDs;\r\n //}\r\n\r\n if (!uploadResponse?.data?.url) {\r\n uploadResponse.url = url; // fallback to original URL if not provided\r\n }\r\n\r\n //console.log('uploadResponse', JSON.stringify(uploadResponse, null, 2));\r\n\r\n return uploadResponse;\r\n \r\n } catch (error) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': error.message,\r\n 'success': false\r\n };\r\n }\r\n\r\n }\r\n\r\n handleBulkUpload = async (type = 'uncompressed', mediaIDs = []) => {\r\n let currentID;\r\n let attachment;\r\n\r\n switch (type) {\r\n case 'all':\r\n case 'uncompressed':\r\n currentID = mediaIDs[0];\r\n break;\r\n case 'path':\r\n currentID = mediaIDs[0]?.filename;\r\n break;\r\n default:\r\n currentID = 0;\r\n break;\r\n }\r\n\r\n if (type === 'path') {\r\n\r\n \r\n\r\n attachment = {\r\n attributes: {\r\n url: mediaIDs[0].url,\r\n mime: mediaIDs[0].mime,\r\n name: mediaIDs[0].name,\r\n filename: mediaIDs[0].filename,\r\n id: mediaIDs[0].id,\r\n sizes: mediaIDs[0]?.sizes,\r\n }\r\n }\r\n\r\n } else {\r\n\r\n const attachmentResponse = await this.getAttachment(currentID); \r\n if (attachmentResponse.success === false) {\r\n if (Array.isArray(mediaIDs)) mediaIDs.shift();\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': attachmentResponse.data\r\n }\r\n }\r\n const attachmentData = attachmentResponse.data;\r\n attachment = {\r\n attributes: {\r\n url: attachmentData.url,\r\n mime: attachmentData.mime,\r\n name: attachmentData.name,\r\n filename: attachmentData.filename,\r\n id: attachmentData.id,\r\n sizes: attachmentData.sizes,\r\n }\r\n }\r\n\r\n }\r\n\r\n if (Array.isArray(mediaIDs)) mediaIDs.shift();\r\n\r\n const mediaType = attachment.attributes.mime.split(\"/\")[0];\r\n const mediaSubType = attachment.attributes.mime.split(\"/\")[1];\r\n\r\n if (!(0,_helpers_js__WEBPACK_IMPORTED_MODULE_0__.maybeCompressAttachment)(mediaType, mediaSubType)) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': __('Skipped', 'squeeze')\r\n }\r\n }\r\n\r\n try {\r\n const compressData = await this.handleCompress( attachment, type );\r\n const uploadData = await this.handleUpload({ attachment: attachment, base64: compressData, type: type, mediaIDs: mediaIDs })\r\n\r\n return uploadData;\r\n\r\n } catch (error) {\r\n return {\r\n 'mediaIDs': mediaIDs,\r\n 'data': error?.message ?? error,\r\n 'success': false\r\n }\r\n }\r\n\r\n }\r\n\r\n \r\n\r\n handleRestore = async (attachmentID) => {\r\n const data = {\r\n action: 'squeeze_restore_attachment',\r\n _ajax_nonce: this.nonce,\r\n attachmentID: attachmentID,\r\n };\r\n\r\n const response = await jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n };\r\n\r\n // Get list of attachments by path\r\n getAttachmentsByPath = async (path) => {\r\n\r\n const data = {\r\n action: 'squeeze_get_attachment_by_path',\r\n path: path,\r\n _ajax_nonce: this.nonce,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getAttachment = async (attachmentID) => {\r\n const data = {\r\n action: 'squeeze_get_attachment',\r\n _ajax_nonce: this.nonce,\r\n attachmentID: attachmentID,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getNextAttachments = async (page = 1, type = 'uncompressed', lastId = 0) => {\r\n const data = {\r\n action: 'squeeze_get_next_attachments',\r\n _ajax_nonce: this.nonce,\r\n page: page,\r\n type: type,\r\n lastId: lastId,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n getDirectories = async (parentDir = false) => {\r\n const data = {\r\n action: 'squeeze_get_directories',\r\n _ajax_nonce: this.nonce,\r\n }\r\n\r\n if (parentDir) {\r\n data['parentDir'] = parentDir;\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n \r\n\r\n setOptions = async (options) => {\r\n const data = {\r\n action: 'squeeze_set_options',\r\n _ajax_nonce: this.nonce,\r\n options: options,\r\n }\r\n\r\n const response = jQuery.ajax({\r\n url: this.ajaxUrl,\r\n type: 'POST',\r\n data: data,\r\n });\r\n\r\n return response;\r\n }\r\n\r\n /**\r\n * Compresses an image before upload.\r\n * If the compressSizes parameter is provided, it will compress the sizes specified along with the original image.\r\n * \r\n * @param {File} file - The file to be compressed\r\n * @param {object} compressSizes - Optional sizes for compression\r\n * @returns {Promise<object|boolean>} - Returns a Promise that resolves to the compressed file as a base64 object or false if the file is invalid\r\n */\r\n compressBeforeUpload = async (file, compressSizes = null) => {\r\n if (!(file instanceof Blob) && !(file instanceof File)) {\r\n console.error('Invalid file type:', file);\r\n return false; // Return false if the file is not a Blob or File\r\n }\r\n\r\n const attachment = {\r\n attributes: {\r\n url: null,\r\n mime: file.type,\r\n name: file.name,\r\n filename: file.name,\r\n id: 0,\r\n sizes: compressSizes,\r\n file: file,\r\n skipFull: false,\r\n }\r\n }\r\n\r\n try {\r\n const base64Obj = await this.handleCompress(attachment);\r\n\r\n return base64Obj; // Return the compressed file as base64Obj\r\n } catch (error) {\r\n console.error(error);\r\n return error; // Return the error for further handling\r\n }\r\n }\r\n\r\n isWebpEncodingSupported = async () => {\r\n try {\r\n const canvas = document.createElement('canvas');\r\n if (!canvas.toDataURL) return false;\r\n const data = canvas.toDataURL('image/webp');\r\n // WebP signature starts with \"data:image/webp\"\r\n return data.indexOf('data:image/webp') === 0;\r\n } catch (e) {\r\n return false;\r\n }\r\n }\r\n\r\n handleConvertFileToWebp = async (args) => {\r\n const timeout = this.timeout;\r\n \r\n const { file, sourceType, name } = args;\r\n const fileBuffer = await file.arrayBuffer();\r\n const mime = sourceType === 'png' ? 'image/png' : 'image/jpeg';\r\n const inputBlob = new Blob([fileBuffer], { type: mime });\r\n // compression options you can tune\r\n const imageCompressionOptions = {\r\n useWebWorker: true,\r\n fileType: 'image/webp',\r\n };\r\n\r\n return new Promise(async (resolve, reject) => {\r\n const timeoutId = setTimeout(() => {\r\n console.warn('Worker convertToWebp terminated', name);\r\n reject(new Error(__('Request timed out.', 'squeeze')));\r\n }, timeout);\r\n\r\n try {\r\n\r\n const compressedBlob = await (0,browser_image_compression__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(inputBlob, imageCompressionOptions);\r\n\r\n // quick checks\r\n //console.log('compressedBlob.type, size:', compressedBlob.type, compressedBlob.size);\r\n // normalize final binary\r\n const webpArrayBuffer = await compressedBlob.arrayBuffer();\r\n\r\n const webpName = name.replace(/\\.\\w+$/, '.webp');\r\n const webpFile = new File([webpArrayBuffer], webpName, { type: 'image/webp', lastModified: Date.now() });\r\n\r\n clearTimeout(timeoutId);\r\n resolve({ webpFile }); // must return object with webpFile prop\r\n } catch (error) {\r\n clearTimeout(timeoutId);\r\n console.error('Error during image processing:', error);\r\n reject(error);\r\n }\r\n });\r\n \r\n }\r\n\r\n // helper: normalize buffer-like inputs to Uint8Array\r\n toUint8Array = (bufferLike) => {\r\n if (bufferLike instanceof Uint8Array) return bufferLike;\r\n if (bufferLike instanceof ArrayBuffer) return new Uint8Array(bufferLike);\r\n if (ArrayBuffer.isView(bufferLike)) return new Uint8Array(bufferLike.buffer, bufferLike.byteOffset, bufferLike.byteLength);\r\n // Node Buffer (in some environments) has .buffer and .byteOffset\r\n if (bufferLike && typeof bufferLike === 'object' && bufferLike.buffer) {\r\n return new Uint8Array(bufferLike.buffer, bufferLike.byteOffset || 0, bufferLike.byteLength || bufferLike.length);\r\n }\r\n throw new Error('Unsupported buffer type: ' + Object.prototype.toString.call(bufferLike));\r\n }\r\n\r\n /**\r\n * Convert an image File to WebP.\r\n *\r\n * @param {File} inputFile — the original image File (e.g. JPEG, PNG).\r\n * @returns {Promise<File>} — a Promise that resolves to a WebP File.\r\n */\r\n convertFileToWebp = async (inputFile) => {\r\n // Ensure it’s an image\r\n if (!inputFile.type.startsWith('image/')) {\r\n throw new Error('Input must be an image File');\r\n }\r\n if (this.isCanvasExtractionBlocked()) {\r\n console.warn('Canvas extraction is blocked');\r\n // Handle the case where canvas extraction is blocked\r\n alert(__('Image conversion blocked by browser privacy setting', 'squeeze'));\r\n throw new Error('Canvas extraction is blocked');\r\n } else {\r\n console.log('Canvas extraction is allowed');\r\n }\r\n const args = {\r\n file: inputFile,\r\n sourceType: inputFile.type.split('/')[1], // e.g. 'jpeg', 'png'\r\n name: inputFile.name,\r\n }\r\n const webpSupported = await this.isWebpEncodingSupported();\r\n try {\r\n // Convert the image to WebP\r\n let webpData;\r\n if (webpSupported) {\r\n //console.log('WebP decoding is supported');\r\n webpData = await this.handleConvertFileToWebp(args);\r\n } else {\r\n console.warn('WebP encoding not supported on this device. Using original format instead.');\r\n const webpName = args.name.replace(/\\.\\w+$/, '.webp');\r\n webpData = await this.convertToWebpWithJsquash(inputFile, webpName);\r\n }\r\n if (!webpData || !webpData.webpFile) {\r\n throw new Error('Failed to convert image to WebP');\r\n }\r\n\r\n return webpData.webpFile; // Return the WebP File\r\n\r\n } catch (error) {\r\n console.error('Error converting image to WebP:', error);\r\n throw error; // Re-throw the error for further handling\r\n }\r\n\r\n }\r\n\r\n /**\r\n * Convert an input (File | Blob | ArrayBuffer | Uint8Array) to a WebP File using @jsquash/webp.\r\n * @param {File|Blob|ArrayBuffer|Uint8Array} input\r\n * @param {string} outputName - desired filename for the .webp output\r\n * @param {{quality?: number, lossless?: boolean, method?: number}} [opts] - encoder options (quality 0-100)\r\n * @returns {Promise<File>} - WebP File\r\n */\r\n convertToWebpWithJsquash = async (input, outputName = 'out.webp', opts = { quality: 100 }) => {\r\n // Normalize input to Blob\r\n let blob;\r\n if (input instanceof Blob) {\r\n blob = input;\r\n } else if (input instanceof ArrayBuffer || ArrayBuffer.isView(input)) {\r\n blob = new Blob([input]);\r\n } else {\r\n throw new Error('Unsupported input type. Provide File, Blob, ArrayBuffer or TypedArray.');\r\n }\r\n\r\n // Load image into <img>\r\n const dataURL = await new Promise((resolve, reject) => {\r\n const fr = new FileReader();\r\n fr.onload = () => resolve(fr.result);\r\n fr.onerror = reject;\r\n fr.readAsDataURL(blob);\r\n });\r\n\r\n const img = await new Promise((resolve, reject) => {\r\n const i = new Image();\r\n i.onload = () => resolve(i);\r\n i.onerror = () => reject(new Error('Failed to load image for conversion'));\r\n i.src = dataURL;\r\n // avoid cross-origin taint if data-url is used\r\n });\r\n\r\n // Draw to canvas and extract ImageData\r\n const canvas = document.createElement('canvas');\r\n canvas.width = img.naturalWidth || img.width;\r\n canvas.height = img.naturalHeight || img.height;\r\n const ctx = canvas.getContext('2d');\r\n ctx.drawImage(img, 0, 0);\r\n const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\r\n\r\n // encode -> returns ArrayBuffer (WASM libwebp)\r\n // opts example: { quality: 75, lossless: false, method: 4 }\r\n const webpArrayBuffer = await (0,_jsquash_webp__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(imageData, opts);\r\n\r\n // Validate a little (size)\r\n if (!webpArrayBuffer || webpArrayBuffer.byteLength < 20) {\r\n throw new Error('jsquash produced an invalid WebP buffer');\r\n }\r\n\r\n // Wrap into File\r\n const webpFile = new File([webpArrayBuffer], outputName.replace(/\\.\\w+$/, '.webp'), {\r\n type: 'image/webp',\r\n lastModified: Date.now(),\r\n });\r\n\r\n return {webpFile};\r\n }\r\n\r\n isCanvasExtractionBlocked = () => {\r\n try {\r\n const c = document.createElement('canvas');\r\n c.width = 2;\r\n c.height = 2;\r\n const ctx = c.getContext('2d');\r\n if (!ctx) return false; // no ctx => treat as not-blocked here (or handle separately)\r\n\r\n // draw four known opaque pixels (no alpha surprise)\r\n ctx.fillStyle = 'rgb(255,0,0)'; // top-left\r\n ctx.fillRect(0, 0, 1, 1);\r\n\r\n ctx.fillStyle = 'rgb(0,255,0)'; // top-right\r\n ctx.fillRect(1, 0, 1, 1);\r\n\r\n ctx.fillStyle = 'rgb(0,0,255)'; // bottom-left\r\n ctx.fillRect(0, 1, 1, 1);\r\n\r\n ctx.fillStyle = 'rgb(1,2,3)'; // bottom-right (small values detect naive normalization)\r\n ctx.fillRect(1, 1, 1, 1);\r\n\r\n let data;\r\n try {\r\n data = ctx.getImageData(0, 0, 2, 2).data; // Uint8ClampedArray length 16 (4*2*2)\r\n } catch (err) {\r\n // thrown security/blocked error (Firefox/resistFingerprinting sometimes throws)\r\n if (err && (err.name === 'SecurityError' || /blocked/i.test(err.message))) return true;\r\n // other errors: be conservative and return false\r\n return false;\r\n }\r\n\r\n // Expected RGBA order for pixels (top-left, top-right, bottom-left, bottom-right)\r\n const expected = new Uint8ClampedArray([\r\n 255, 0, 0, 255, // top-left\r\n 0, 255, 0, 255, // top-right\r\n 0, 0, 255, 255, // bottom-left\r\n 1, 2, 3, 255 // bottom-right\r\n ]);\r\n\r\n // Compare each channel exactly. If ANY mismatch — canvas data was altered.\r\n for (let i = 0; i < expected.length; i++) {\r\n if (data[i] !== expected[i]) {\r\n // mismatch -> either fingerprint-protection normalized the canvas OR something else tainted it\r\n return true;\r\n }\r\n }\r\n\r\n // No thrown error and pixels match exactly => extraction works\r\n return false;\r\n } catch (e) {\r\n // If something unexpected goes wrong, assume not blocked so caller can handle fallback.\r\n return false;\r\n }\r\n }\r\n\r\n\r\n}\n\n//# sourceURL=webpack:///./assets/js/squeeze.js?"); 50 50 51 51 /***/ }), -
squeeze/trunk/readme.txt
r3345284 r3346520 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.3 7 Stable tag: 1.7. 07 Stable tag: 1.7.1 8 8 License: GPLv3 9 9 License URI: https://www.gnu.org/licenses/gpl-3.0.html … … 99 99 That method is not very optimal, because it creates extra images which reduce your server's storage space. 100 100 However, if you already used that approach, you can keep it enabled for backwards compatibility. 101 102 = Why am I seeing an alert "Image conversion blocked by browser privacy setting"? = 103 104 That means your browser is blocking access to canvas image data (this prevents image conversion). 105 The **privacy.resistFingerprinting** setting (or similar privacy features) is enabled. 106 107 **What you can do**: 108 109 * Open `about:config` in Firefox and set `privacy.resistFingerprinting` to `false`, then restart Firefox. 110 * Use a different browser (Chrome, Edge) or a browser profile without this setting. 101 111 102 112 = Which scripts are used for compressing and converting images? … … 151 161 152 162 == Changelog == 163 = 1.7.1 = 164 * Fixed Editor's interception request bug 165 * Add notification if Canvas extraction is blocked 153 166 = 1.7.0 = 154 167 * Added direct WEBP conversion option … … 228 241 229 242 == Upgrade Notice == 243 = 1.7.1 = 244 * Fixed Editor's interception request bug 245 * Add notification if Canvas extraction is blocked 230 246 = 1.7.0 = 231 247 * Added direct WEBP conversion option -
squeeze/trunk/squeeze.php
r3345284 r3346520 6 6 * Author URI: https://pluginarium.com 7 7 * Author: Bogdan Bendziukov 8 * Version: 1.7. 08 * Version: 1.7.1 9 9 * 10 10 * Text Domain: squeeze … … 27 27 * Plugin version 28 28 */ 29 const VERSION = '1.7. 0';29 const VERSION = '1.7.1'; 30 30 31 31 const CHECKOUT_URL = 'https://checkout.freemius.com/plugin/17217/plan/28703/';
Note: See TracChangeset
for help on using the changeset viewer.