1'use strict' 2 3function canvasToBlobSafe(canvas, type, quality) { 4 return new Promise((resolve) => { 5 try { 6 canvas.toBlob((blob) => { 7 if (blob) return resolve(blob); 8 // fallback: toDataURL -> Blob 9 try { 10 const dataURL = canvas.toDataURL(type, quality); 11 const arr = dataURL.split(','); 12 const mime = arr[0].match(/:(.*?);/)[1] || type || 'image/jpeg'; 13 const bstr = atob(arr[1]); 14 let n = bstr.length; 15 const u8arr = new Uint8Array(n); 16 while (n--) u8arr[n] = bstr.charCodeAt(n); 17 resolve(new Blob([u8arr], { type: mime })); 18 } catch (_) { 19 resolve(null); 20 } 21 }, type, quality); 22 } catch (_) { 23 resolve(null); 24 } 25 }); 26} 27 28async function stripExifToBlob(file) { 29 const mime = (file && file.type) || ''; 30 const name = (file && file.name) || ''; 31 const isJpeg = /jpe?g$/i.test(name) || /^image\/p?jpeg$/i.test(mime); 32 const isPng = /png$/i.test(name) || /^image\/png$/i.test(mime); 33 34 // ТолÑко JPEG/PNG ÑеалÑно ÑиÑÑим; оÑÑалÑное â без изменений 35 if (!isJpeg && !isPng) return file; 36 37 let src, w, h, revoke; 38 try { 39 if (window.createImageBitmap) { 40 const bmp = await createImageBitmap(file, { imageOrientation: 'from-image' }); 41 src = bmp; w = bmp.width; h = bmp.height; 42 revoke = () => bmp.close && bmp.close(); 43 } 44 } catch (_) { /* fallback ниже */ } 45 46 if (!src) { 47 const url = URL.createObjectURL(file); 48 const img = new Image(); 49 img.decoding = 'async'; 50 // crossOrigin не нÑжен Ð´Ð»Ñ blob: URL, но не меÑÐ°ÐµÑ 51 await new Promise((res, rej) => { img.onload = res; img.onerror = rej; img.src = url; }); 52 src = img; w = img.naturalWidth; h = img.naturalHeight; 53 revoke = () => URL.revokeObjectURL(url); 54 } 55 56 const targetType = isPng ? 'image/png' : 'image/jpeg'; 57 const quality = isPng ? undefined : 0.92; 58 59 const canvas = document.createElement('canvas'); 60 canvas.width = w; canvas.height = h; 61 const ctx = canvas.getContext('2d'); 62 ctx.drawImage(src, 0, 0, w, h); 63 64 if (revoke) { 65 revoke(); 66 } 67 68 const blob = await canvasToBlobSafe(canvas, targetType, quality); 69 // ÐÑли ÑÑо-Ñо поÑло не Ñак â веÑнÑм иÑÑ Ð¾Ð´Ð½Ñй Ñайл, ÑÑÐ¾Ð±Ñ Ð½Ðµ ломаÑÑ UX 70 return blob || file; 71} 72 73async function prepareUploadFile(origFile) { 74 const type = origFile.type || ''; 75 const name = origFile.name || 'image'; 76 77 // GIF оÑÑавлÑем как еÑÑÑ (EXIF Ñам пÑакÑиÑеÑки не бÑваеÑ, анимаÑÐ¸Ñ ÑеÑÑÑÑ Ð½ÐµÐ»ÑзÑ) 78 if (/^image\/gif$/i.test(type) || /\.gif$/i.test(name)) { 79 return origFile; 80 } 81 82 // WEBP: еÑли ÑеÑÐ²ÐµÑ Ð½Ðµ дÑÑÐ¶Ð¸Ñ â можно конвеÑÑнÑÑÑ Ð² JPEG 83 // (закомменÑиÑÑй ÑÑо ÑÑловие, еÑли WEBP ок на бÑке) 84 if (/^image\/webp$/i.test(type) || /\.webp$/i.test(name)) { 85 // ÐеÑекодиÑÑем ÑеÑез canvas в JPEG, меÑаданнÑе обнÑлÑÑÑÑ 86 const blob = await stripExifToBlob(new File([origFile], name.replace(/\.webp$/i, '.jpg'), { type: 'image/jpeg' })); 87 if (blob instanceof Blob) { 88 return new File([blob], name.replace(/\.\w+$/,'') + '.jpg', { type: 'image/jpeg', lastModified: Date.now() }); 89 } 90 return origFile; 91 } 92 93 // JPEG/PNG â ÑиÑÑим 94 const cleaned = await stripExifToBlob(origFile); 95 96 // ÐÑли веÑнÑлÑÑ Ñже File â оÑдаÑм как еÑÑÑ 97 if (cleaned instanceof File) return cleaned; 98 99 // ÐÑли веÑнÑлÑÑ Blob â обоÑаÑиваем в File 100 if (cleaned instanceof Blob) { 101 const isPng = cleaned.type && /^image\/png$/i.test(cleaned.type); 102 const ext = isPng ? 'png' : 'jpg'; 103 const typeOut = isPng ? 'image/png' : 'image/jpeg'; 104 const stem = name.replace(/\.(jpe?g|png|gif|webp)$/i, '') || 'image'; 105 return new File([cleaned], `${stem}.${ext}`, { type: typeOut, lastModified: Date.now() }); 106 } 107 108 // Fallback: ниÑего не полÑÑилоÑÑ â ÑлÑм оÑигинал 109 return origFile; 110} 111 112function decodeHtmlEntitiesForInput(value) { 113 if (value === null || typeof value === 'undefined') { 114 return ''; 115 } 116 117 const normalized = String(value) 118 .replace(/&\s+([a-zA-Z][a-zA-Z0-9#]+)\s*;/g, '&$1;') 119 .replace(/&([a-zA-Z][a-zA-Z0-9#]+)\s+;/g, '&$1;'); 120 121 const textarea = document.createElement('textarea'); 122 textarea.innerHTML = normalized; 123 return (textarea.value || textarea.textContent || '').normalize('NFC').trim(); 124} 125 126module.exports = { 127 prepareUploadFile, 128 stripExifToBlob, 129 decodeHtmlEntitiesForInput 130}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.