fix: preserve original file type on upload — never convert PNG to JPG
Problem: cropper always called out.toBlob(..., 'image/jpeg') regardless of the original file type, silently converting PNGs to JPGs. Fix: - openCropper() now stores file.type and file.name on the cropper object - applyCrop() uses the stored mime type for toBlob() and the filename - Quality param only passed for lossy formats (jpeg/webp), not for PNG/GIF - uploadImage() accept list expanded: svg, ico allowed - Server-side: .svg and .ico added to allowed extensions Result: PNG stays PNG, WebP stays WebP, ICO stays ICO. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -589,7 +589,7 @@ app.MapPost("/api/upload", async (HttpRequest request, IWebHostEnvironment env)
|
|||||||
if (!request.HasFormContentType || !request.Form.Files.Any())
|
if (!request.HasFormContentType || !request.Form.Files.Any())
|
||||||
return Results.BadRequest("No file provided.");
|
return Results.BadRequest("No file provided.");
|
||||||
var file = request.Form.Files[0];
|
var file = request.Form.Files[0];
|
||||||
var allowed = new[] { ".jpg", ".jpeg", ".png", ".webp", ".gif" };
|
var allowed = new[] { ".jpg", ".jpeg", ".png", ".webp", ".gif", ".svg", ".ico" };
|
||||||
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
|
var ext = Path.GetExtension(file.FileName).ToLowerInvariant();
|
||||||
if (!allowed.Contains(ext)) return Results.BadRequest("File type not allowed.");
|
if (!allowed.Contains(ext)) return Results.BadRequest("File type not allowed.");
|
||||||
var uploadsDir = Path.Combine(env.WebRootPath, "uploads");
|
var uploadsDir = Path.Combine(env.WebRootPath, "uploads");
|
||||||
|
|||||||
@@ -1690,6 +1690,9 @@ const cropper = {
|
|||||||
|
|
||||||
function openCropper(inputId, previewId, file) {
|
function openCropper(inputId, previewId, file) {
|
||||||
cropper.inputId = inputId; cropper.previewId = previewId;
|
cropper.inputId = inputId; cropper.previewId = previewId;
|
||||||
|
// Preserve original file type so PNG stays PNG, WebP stays WebP, etc.
|
||||||
|
cropper.mimeType = file.type || 'image/jpeg';
|
||||||
|
cropper.fileName = file.name || 'upload';
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = e => {
|
reader.onload = e => {
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
@@ -1831,7 +1834,8 @@ async function applyCrop() {
|
|||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
try {
|
try {
|
||||||
const fd = new FormData();
|
const fd = new FormData();
|
||||||
fd.append('file', new File([blob], 'crop.jpg', {type:'image/jpeg'}));
|
const ext = _inputId && cropper.mimeType ? cropper.mimeType.split('/')[1].replace('jpeg','jpg') : 'jpg';
|
||||||
|
fd.append('file', new File([blob], `crop.${ext}`, {type: cropper.mimeType || 'image/jpeg'}));
|
||||||
const r = await fetch('/api/upload', {method:'POST', headers:{'Authorization':`Bearer ${token}`}, body:fd});
|
const r = await fetch('/api/upload', {method:'POST', headers:{'Authorization':`Bearer ${token}`}, body:fd});
|
||||||
if (!r.ok) throw new Error(await r.text());
|
if (!r.ok) throw new Error(await r.text());
|
||||||
const { url } = await r.json();
|
const { url } = await r.json();
|
||||||
@@ -1840,13 +1844,16 @@ async function applyCrop() {
|
|||||||
toast('تصویر با موفقیت آپلود شد ✓');
|
toast('تصویر با موفقیت آپلود شد ✓');
|
||||||
} catch(e) { toast('خطا در آپلود: '+e.message,'error'); }
|
} catch(e) { toast('خطا در آپلود: '+e.message,'error'); }
|
||||||
finally { btn.innerHTML=orig; btn.disabled=false; }
|
finally { btn.innerHTML=orig; btn.disabled=false; }
|
||||||
}, 'image/jpeg', 0.92);
|
// Use original mime type; only pass quality for lossy formats
|
||||||
|
const mime = cropper.mimeType || 'image/jpeg';
|
||||||
|
const quality = (mime === 'image/jpeg' || mime === 'image/webp') ? 0.92 : undefined;
|
||||||
|
}, mime, quality);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadImage(inputId, previewId) {
|
async function uploadImage(inputId, previewId) {
|
||||||
const fileInput = document.createElement('input');
|
const fileInput = document.createElement('input');
|
||||||
fileInput.type = 'file';
|
fileInput.type = 'file';
|
||||||
fileInput.accept = 'image/jpeg,image/png,image/webp,image/gif';
|
fileInput.accept = 'image/jpeg,image/png,image/webp,image/gif,image/svg+xml,image/x-icon,image/vnd.microsoft.icon';
|
||||||
fileInput.onchange = () => {
|
fileInput.onchange = () => {
|
||||||
const file = fileInput.files[0];
|
const file = fileInput.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|||||||
Reference in New Issue
Block a user