import type { SilkeIcons } from '../silke-icon/silke-icon'; import { SilkeFileValue, SilkeImageValue } from './utils'; /** File type category for the acceptedFileTypes prop */ export type SilkeFileCategory = 'image' | 'text'; /** MIME patterns for each file category */ const FILE_CATEGORY_PATTERNS: Record = { image: ['image/*'], text: [ 'text/plain', 'text/csv', 'text/markdown', 'text/html', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/json', ], }; /** Resolve file categories to a flat list of MIME patterns */ export function resolveFileCategories(categories: SilkeFileCategory[]): string[] { return categories.flatMap((cat) => FILE_CATEGORY_PATTERNS[cat] ?? []); } /** * Check if a MIME type matches a set of MIME patterns. * Supports exact matches ('text/csv') and wildcard subtypes ('image/*'). */ export function matchesMimePattern(mimeType: string, patterns: string[]): boolean { return patterns.some((pattern) => { if (pattern === '*/*') return true; if (pattern.endsWith('/*')) { const prefix = pattern.slice(0, pattern.indexOf('/')); return mimeType.startsWith(prefix + '/'); } return mimeType === pattern; }); } /** * Reads a File and converts it to a SilkeImageValue or SilkeFileValue. * Images produce SilkeImageValue for backward compatibility; other files produce SilkeFileValue. * Returns null if the file type is not in the accepted patterns. */ export function fileToAttachmentValue( file: File, acceptedTypes: string[], ): Promise { return new Promise((resolve) => { if (!matchesMimePattern(file.type, acceptedTypes)) { resolve(null); return; } const reader = new FileReader(); reader.onload = (event) => { const result = event.target?.result; if (typeof result === 'string') { const [, base64Data] = result.split(','); if (file.type.startsWith('image/')) { resolve({ type: 'image', data: base64Data, mimeType: file.type, name: file.name || undefined, }); } else { resolve({ type: 'file', data: base64Data, mimeType: file.type, name: file.name || 'Untitled', }); } } else { resolve(null); } }; reader.onerror = () => resolve(null); reader.readAsDataURL(file); }); } /** * Extract the first matching file from a ClipboardEvent given accepted MIME patterns. */ export function getFileFromClipboard( e: React.ClipboardEvent, acceptedTypes: string[], ): File | null { const items = e.clipboardData?.items; if (!items) return null; for (const item of items) { if (matchesMimePattern(item.type, acceptedTypes)) { return item.getAsFile(); } } return null; } /** * Extract the first matching file from a DragEvent given accepted MIME patterns. */ export function getFileFromDragEvent( e: DragEvent, acceptedTypes: string[], ): File | null { const files = e.dataTransfer?.files; if (!files || files.length === 0) return null; for (const file of files) { if (matchesMimePattern(file.type, acceptedTypes)) { return file; } } return null; } /** * Maps a MIME type to a SilkeIcon key for display. */ export function getFileIcon(mimeType: string): SilkeIcons { if (mimeType === 'application/pdf') return 'file'; if ( mimeType === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || mimeType === 'application/vnd.ms-excel' ) { return 'file'; } if ( mimeType === 'application/msword' || mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ) { return 'file'; } if (mimeType.startsWith('text/')) return 'file'; return 'file'; } /** * Truncate a filename for display, preserving the extension. */ export function truncateFilename(name: string, maxLength = 20): string { if (name.length <= maxLength) return name; const dotIndex = name.lastIndexOf('.'); const ext = dotIndex >= 0 ? name.slice(dotIndex) : ''; const stem = name.slice(0, name.length - ext.length); const available = maxLength - ext.length - 3; // 3 for "..." if (available <= 0) return name.slice(0, maxLength - 3) + '...'; return stem.slice(0, available) + '...' + ext; }