검증게시판, 압축파일, 한글파일 첨부가능

This commit is contained in:
Macbook
2026-06-13 01:44:49 +09:00
parent beeebed8f4
commit 864d85484d
7 changed files with 297 additions and 73 deletions
@@ -0,0 +1,100 @@
/**
* 검증게시판 첨부 — 이미지 + zip·hwp·hwpx
*/
import {
pickImageFiles,
prepareImagesForUpload,
} from './clipboardImageFiles';
export const VERIFICATION_ATTACHMENT_ACCEPT =
'image/jpeg,image/png,image/gif,image/webp,image/bmp,.zip,.hwp,.hwpx,application/zip,application/x-hwp,application/vnd.hancom.hwpx';
const DOCUMENT_EXT = new Set(['.zip', '.hwp', '.hwpx']);
const DOCUMENT_MIME = new Set([
'application/zip',
'application/x-zip-compressed',
'application/x-hwp',
'application/haansoft-hwp',
'application/vnd.hancom.hwp',
'application/hwp+zip',
'application/vnd.hancom.hwpx',
'application/x-hwpx',
]);
function extOf(name: string): string {
const i = name.lastIndexOf('.');
return i >= 0 ? name.slice(i).toLowerCase() : '';
}
function mimeForDocumentExt(ext: string): string | null {
switch (ext) {
case '.zip': return 'application/zip';
case '.hwp': return 'application/x-hwp';
case '.hwpx': return 'application/vnd.hancom.hwpx';
default: return null;
}
}
export function isImageAttachment(contentType?: string | null, fileName?: string | null): boolean {
const mime = (contentType ?? '').split(';')[0]?.trim().toLowerCase();
if (mime.startsWith('image/')) return true;
const ext = fileName ? extOf(fileName) : '';
return ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp'].includes(ext);
}
export function isDocumentAttachmentFile(file: File): boolean {
const ext = extOf(file.name);
if (DOCUMENT_EXT.has(ext)) return true;
const mime = file.type.split(';')[0]?.trim().toLowerCase();
return DOCUMENT_MIME.has(mime);
}
function normalizeDocumentFile(file: File): File | null {
if (file.size === 0) return null;
const ext = extOf(file.name);
if (!DOCUMENT_EXT.has(ext)) {
const mime = file.type.split(';')[0]?.trim().toLowerCase();
if (!DOCUMENT_MIME.has(mime)) return null;
}
const mime = mimeForDocumentExt(ext)
?? file.type.split(';')[0]?.trim().toLowerCase()
?? 'application/octet-stream';
if (file.type === mime) return file;
return new File([file], file.name, { type: mime, lastModified: file.lastModified });
}
/** FileList / File[] → 허용 첨부만 (이미지·zip·hwp·hwpx) */
export function pickAttachmentFiles(list: FileList | File[]): File[] {
const out: File[] = [];
for (const file of Array.from(list)) {
if (isDocumentAttachmentFile(file)) {
const doc = normalizeDocumentFile(file);
if (doc) out.push(doc);
continue;
}
const img = pickImageFiles([file])[0];
if (img) out.push(img);
}
return out;
}
const MAX_BYTES = 5 * 1024 * 1024;
/** 이미지는 압축·문서는 그대로 업로드 */
export async function prepareAttachmentsForUpload(files: File[]): Promise<File[]> {
const picked = pickAttachmentFiles(files);
const out: File[] = [];
for (const f of picked) {
if (isDocumentAttachmentFile(f)) {
if (f.size > MAX_BYTES) throw new Error('FILE_TOO_LARGE');
out.push(f);
} else {
out.push(...await prepareImagesForUpload([f]));
}
}
return out;
}
export const ATTACHMENT_TYPE_HINT =
'jpeg, png, gif, webp, bmp, zip, hwp, hwpx (파일당 5MB)';
+1 -1
View File
@@ -195,7 +195,7 @@ function parseVerificationUploadError(status: number, text: string): string {
if (status === 413) {
return '업로드 용량이 너무 큽니다. 이미지를 줄이거나 5MB 이하 파일을 사용하세요.';
}
if (status === 400) return '이미지 형식 또는 크기를 확인하세요. (jpeg, png, gif, webp, bmp · 파일당 5MB)';
if (status === 400) return '파일 형식 또는 크기를 확인하세요. (jpeg, png, gif, webp, bmp, zip, hwp, hwpx · 파일당 5MB)';
return `이미지 업로드에 실패했습니다. (${status})`;
}