mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
fix: detect previewable attachments by file extension when MIME type is octet-stream #309
This commit is contained in:
@@ -37,6 +37,43 @@ import { getFileConfig } from './mail-message-view';
|
|||||||
const PREVIEWABLE_IMAGE = /^image\/(png|jpeg|gif|webp|svg\+xml)$/;
|
const PREVIEWABLE_IMAGE = /^image\/(png|jpeg|gif|webp|svg\+xml)$/;
|
||||||
const PREVIEWABLE_TEXT = /^(text\/(plain|csv|html|xml|css|javascript|markdown)|application\/(json|xml|javascript|x-httpd-php|x-sh|x-perl|x-python|x-ruby))$/;
|
const PREVIEWABLE_TEXT = /^(text\/(plain|csv|html|xml|css|javascript|markdown)|application\/(json|xml|javascript|x-httpd-php|x-sh|x-perl|x-python|x-ruby))$/;
|
||||||
|
|
||||||
|
const EXT_TO_MIME: Record<string, string> = {
|
||||||
|
'.pdf': 'application/pdf',
|
||||||
|
'.png': 'image/png',
|
||||||
|
'.jpg': 'image/jpeg',
|
||||||
|
'.jpeg': 'image/jpeg',
|
||||||
|
'.gif': 'image/gif',
|
||||||
|
'.webp': 'image/webp',
|
||||||
|
'.svg': 'image/svg+xml',
|
||||||
|
'.txt': 'text/plain',
|
||||||
|
'.csv': 'text/csv',
|
||||||
|
'.html': 'text/html',
|
||||||
|
'.htm': 'text/html',
|
||||||
|
'.xml': 'application/xml',
|
||||||
|
'.css': 'text/css',
|
||||||
|
'.js': 'application/javascript',
|
||||||
|
'.mjs': 'application/javascript',
|
||||||
|
'.md': 'text/markdown',
|
||||||
|
'.json': 'application/json',
|
||||||
|
'.php': 'application/x-httpd-php',
|
||||||
|
'.sh': 'application/x-sh',
|
||||||
|
'.pl': 'application/x-perl',
|
||||||
|
'.py': 'application/x-python',
|
||||||
|
'.rb': 'application/x-ruby',
|
||||||
|
};
|
||||||
|
|
||||||
|
function getExtensionMime(filename: string): string | null {
|
||||||
|
const dot = filename.lastIndexOf('.');
|
||||||
|
if (dot < 0) return null;
|
||||||
|
const ext = filename.slice(dot).toLowerCase();
|
||||||
|
return EXT_TO_MIME[ext] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveContentType(contentType: string, fileName: string): string {
|
||||||
|
if (contentType && contentType !== 'application/octet-stream') return contentType;
|
||||||
|
return getExtensionMime(fileName) ?? contentType;
|
||||||
|
}
|
||||||
|
|
||||||
export interface PreviewAttachment {
|
export interface PreviewAttachment {
|
||||||
content_hash: string;
|
content_hash: string;
|
||||||
file_type: string;
|
file_type: string;
|
||||||
@@ -57,16 +94,16 @@ interface AttachmentPreviewProps {
|
|||||||
attachmentIndex?: number;
|
attachmentIndex?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isImagePreview(contentType: string) {
|
function isImagePreview(contentType: string, fileName?: string) {
|
||||||
return PREVIEWABLE_IMAGE.test(contentType);
|
return PREVIEWABLE_IMAGE.test(resolveContentType(contentType, fileName ?? ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPdfPreview(contentType: string) {
|
function isPdfPreview(contentType: string, fileName?: string) {
|
||||||
return contentType === 'application/pdf';
|
return resolveContentType(contentType, fileName ?? '') === 'application/pdf';
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTextPreview(contentType: string) {
|
function isTextPreview(contentType: string, fileName?: string) {
|
||||||
return PREVIEWABLE_TEXT.test(contentType);
|
return PREVIEWABLE_TEXT.test(resolveContentType(contentType, fileName ?? ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AttachmentPreview({
|
export default function AttachmentPreview({
|
||||||
@@ -90,7 +127,7 @@ export default function AttachmentPreview({
|
|||||||
const imageIndices = useMemo(() => {
|
const imageIndices = useMemo(() => {
|
||||||
if (!attachments) return [];
|
if (!attachments) return [];
|
||||||
return attachments
|
return attachments
|
||||||
.map((a, i) => (isImagePreview(a.file_type) ? i : -1))
|
.map((a, i) => (isImagePreview(a.file_type, a.filename) ? i : -1))
|
||||||
.filter((i) => i >= 0);
|
.filter((i) => i >= 0);
|
||||||
}, [attachments]);
|
}, [attachments]);
|
||||||
|
|
||||||
@@ -139,14 +176,16 @@ export default function AttachmentPreview({
|
|||||||
return () => window.removeEventListener('keydown', handler);
|
return () => window.removeEventListener('keydown', handler);
|
||||||
}, [open, goPrev, goNext]);
|
}, [open, goPrev, goNext]);
|
||||||
|
|
||||||
|
const effectiveType = resolveContentType(resolved.contentType, resolved.fileName);
|
||||||
|
|
||||||
// ── Fetch preview blob ─────────────────────────────────────────
|
// ── Fetch preview blob ─────────────────────────────────────────
|
||||||
const previewMutation = useMutation({
|
const previewMutation = useMutation({
|
||||||
mutationFn: () => preview_attachment(accountId, envelopeId, resolved.contentHash),
|
mutationFn: () => preview_attachment(accountId, envelopeId, resolved.contentHash),
|
||||||
onSuccess: (blob) => {
|
onSuccess: (blob) => {
|
||||||
if (isTextPreview(resolved.contentType)) {
|
if (isTextPreview(resolved.contentType, resolved.fileName)) {
|
||||||
blob.text().then(setTextContent);
|
blob.text().then(setTextContent);
|
||||||
} else {
|
} else {
|
||||||
const typedBlob = new Blob([blob], { type: resolved.contentType });
|
const typedBlob = new Blob([blob], { type: effectiveType });
|
||||||
setBlobUrl(URL.createObjectURL(typedBlob));
|
setBlobUrl(URL.createObjectURL(typedBlob));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -180,9 +219,9 @@ export default function AttachmentPreview({
|
|||||||
|
|
||||||
const { icon } = useMemo(() => getFileConfig(resolved.contentType), [resolved.contentType]);
|
const { icon } = useMemo(() => getFileConfig(resolved.contentType), [resolved.contentType]);
|
||||||
|
|
||||||
const isImage = isImagePreview(resolved.contentType);
|
const isImage = isImagePreview(resolved.contentType, resolved.fileName);
|
||||||
const isPdf = isPdfPreview(resolved.contentType);
|
const isPdf = isPdfPreview(resolved.contentType, resolved.fileName);
|
||||||
const isText = isTextPreview(resolved.contentType);
|
const isText = isTextPreview(resolved.contentType, resolved.fileName);
|
||||||
const showArrows = imageTotal > 1 && isImage;
|
const showArrows = imageTotal > 1 && isImage;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user