| |
| |
| |
|
|
| |
| export const ACCEPTED_IMAGE_TYPES = [ |
| 'image/jpeg', |
| 'image/jpg', |
| 'image/png', |
| 'image/gif', |
| 'image/webp', |
| ]; |
|
|
| |
| export const ACCEPTED_TEXT_TYPES = ['text/plain', 'text/markdown', 'text/x-markdown']; |
|
|
| |
| export const ACCEPTED_TEXT_EXTENSIONS = ['.txt', '.md']; |
|
|
| |
| export const MARKDOWN_EXTENSIONS = ['.md', '.markdown']; |
|
|
| |
| export const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp']; |
|
|
| |
| export const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; |
|
|
| |
| export const DEFAULT_MAX_TEXT_FILE_SIZE = 1 * 1024 * 1024; |
|
|
| |
| export const DEFAULT_MAX_FILES = 5; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function sanitizeFilename(filename: string): string { |
| const lastDot = filename.lastIndexOf('.'); |
| const name = lastDot > 0 ? filename.substring(0, lastDot) : filename; |
| const ext = lastDot > 0 ? filename.substring(lastDot) : ''; |
|
|
| const sanitized = name |
| .replace(/[\s\u00A0\u202F\u2009\u200A]+/g, '_') |
| .replace(/[^a-zA-Z0-9_-]/g, '_') |
| .replace(/_+/g, '_') |
| .replace(/^_|_$/g, ''); |
|
|
| return `${sanitized || 'image'}${ext}`; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function fileToBase64(file: File): Promise<string> { |
| return new Promise((resolve, reject) => { |
| const reader = new FileReader(); |
| reader.onload = () => { |
| if (typeof reader.result === 'string') { |
| resolve(reader.result); |
| } else { |
| reject(new Error('Failed to read file as base64')); |
| } |
| }; |
| reader.onerror = () => reject(new Error('Failed to read file')); |
| reader.readAsDataURL(file); |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function extractBase64Data(dataUrl: string): string { |
| return dataUrl.split(',')[1] || dataUrl; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function formatFileSize(bytes: number): string { |
| if (bytes === 0) return '0 B'; |
| const k = 1024; |
| const sizes = ['B', 'KB', 'MB', 'GB']; |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function validateImageFile( |
| file: File, |
| maxFileSize: number = DEFAULT_MAX_FILE_SIZE |
| ): { isValid: boolean; error?: string } { |
| |
| if (!ACCEPTED_IMAGE_TYPES.includes(file.type)) { |
| return { |
| isValid: false, |
| error: `${file.name}: Unsupported file type. Please use JPG, PNG, GIF, or WebP.`, |
| }; |
| } |
|
|
| |
| if (file.size > maxFileSize) { |
| const maxSizeMB = maxFileSize / (1024 * 1024); |
| return { |
| isValid: false, |
| error: `${file.name}: File too large. Maximum size is ${maxSizeMB}MB.`, |
| }; |
| } |
|
|
| return { isValid: true }; |
| } |
|
|
| |
| |
| |
| |
| |
| export function generateImageId(): string { |
| return `img-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; |
| } |
|
|
| |
| |
| |
| |
| |
| export function generateFileId(): string { |
| return `file-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function isTextFile(file: File): boolean { |
| const extension = file.name.toLowerCase().slice(file.name.lastIndexOf('.')); |
| const isTextExtension = ACCEPTED_TEXT_EXTENSIONS.includes(extension); |
| const isTextMime = ACCEPTED_TEXT_TYPES.includes(file.type); |
| return isTextExtension || isTextMime; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function isImageFile(file: File): boolean { |
| return ACCEPTED_IMAGE_TYPES.includes(file.type); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| export function validateTextFile( |
| file: File, |
| maxFileSize: number = DEFAULT_MAX_TEXT_FILE_SIZE |
| ): { isValid: boolean; error?: string } { |
| const extension = file.name.toLowerCase().slice(file.name.lastIndexOf('.')); |
|
|
| |
| if (!ACCEPTED_TEXT_EXTENSIONS.includes(extension)) { |
| return { |
| isValid: false, |
| error: `${file.name}: Unsupported file type. Please use .txt or .md files.`, |
| }; |
| } |
|
|
| |
| if (file.size > maxFileSize) { |
| const maxSizeMB = maxFileSize / (1024 * 1024); |
| return { |
| isValid: false, |
| error: `${file.name}: File too large. Maximum size is ${maxSizeMB}MB.`, |
| }; |
| } |
|
|
| return { isValid: true }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function fileToText(file: File): Promise<string> { |
| return new Promise((resolve, reject) => { |
| const reader = new FileReader(); |
| reader.onload = () => { |
| if (typeof reader.result === 'string') { |
| resolve(reader.result); |
| } else { |
| reject(new Error('Failed to read file as text')); |
| } |
| }; |
| reader.onerror = () => reject(new Error('Failed to read file')); |
| reader.readAsText(file); |
| }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function getTextFileMimeType(filename: string): string { |
| const extension = filename.toLowerCase().slice(filename.lastIndexOf('.')); |
| if (extension === '.md') { |
| return 'text/markdown'; |
| } |
| return 'text/plain'; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function isMarkdownFilename(filename: string): boolean { |
| const dotIndex = filename.lastIndexOf('.'); |
| if (dotIndex < 0) return false; |
| const ext = filename.toLowerCase().substring(dotIndex); |
| return MARKDOWN_EXTENSIONS.includes(ext); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function isImageFilename(filename: string): boolean { |
| const dotIndex = filename.lastIndexOf('.'); |
| if (dotIndex < 0) return false; |
| const ext = filename.toLowerCase().substring(dotIndex); |
| return IMAGE_EXTENSIONS.includes(ext); |
| } |
|
|