PageSourceSearch

https://static.sponsr.ru/js/utils/index.js

js sponsr.ru collected 2026-10-02 04:10:07 UTC 4,944 bytes, 130 lines download raw bytes

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.