0
# File Handling
1
2
MIME type validation and asynchronous file reading utilities with support for batching and order preservation. These functions provide robust file processing capabilities designed for handling media files and maintaining compatibility with the Lexical editor's history system.
3
4
## Capabilities
5
6
### MIME Type Validation
7
8
Checks if a file matches one or more acceptable MIME types with case-sensitive comparison.
9
10
```typescript { .api }
11
/**
12
* Returns true if the file type matches the types passed within the acceptableMimeTypes array, false otherwise.
13
* The types passed must be strings and are CASE-SENSITIVE.
14
* eg. if file is of type 'text' and acceptableMimeTypes = ['TEXT', 'IMAGE'] the function will return false.
15
* @param file - The file you want to type check.
16
* @param acceptableMimeTypes - An array of strings of types which the file is checked against.
17
* @returns true if the file is an acceptable mime type, false otherwise.
18
*/
19
function isMimeType(
20
file: File,
21
acceptableMimeTypes: Array<string>
22
): boolean;
23
```
24
25
**Usage Examples:**
26
27
```typescript
28
import { isMimeType } from "@lexical/utils";
29
30
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
31
const files = Array.from(fileInput.files || []);
32
33
// Check for image files
34
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
35
const imageFiles = files.filter(file => isMimeType(file, imageTypes));
36
37
// Check for video files
38
const videoTypes = ['video/mp4', 'video/webm', 'video/quicktime'];
39
const videoFiles = files.filter(file => isMimeType(file, videoTypes));
40
41
// Check for text files (case-sensitive)
42
const textTypes = ['text/plain', 'text/markdown', 'text/html'];
43
const textFiles = files.filter(file => isMimeType(file, textTypes));
44
45
// Using MIME type prefixes for broader matching
46
const audioTypes = ['audio/']; // Matches any audio type
47
const audioFiles = files.filter(file => isMimeType(file, audioTypes));
48
49
// Validation with feedback
50
function validateFileType(file: File): { valid: boolean; message: string } {
51
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
52
53
if (isMimeType(file, allowedTypes)) {
54
return { valid: true, message: 'File type accepted' };
55
} else {
56
return {
57
valid: false,
58
message: `File type '${file.type}' not supported. Please upload JPEG, PNG, or PDF files.`
59
};
60
}
61
}
62
```
63
64
### Media File Reader
65
66
Advanced asynchronous file reader with MIME type filtering, batched results, and order preservation for compatibility with Lexical's history system.
67
68
```typescript { .api }
69
/**
70
* Lexical File Reader with:
71
* 1. MIME type support
72
* 2. batched results (HistoryPlugin compatibility)
73
* 3. Order aware (respects the order when multiple Files are passed)
74
*
75
* const filesResult = await mediaFileReader(files, ['image/']);
76
* filesResult.forEach(file => editor.dispatchCommand('INSERT_IMAGE', {
77
* src: file.result,
78
* }));
79
*/
80
function mediaFileReader(
81
files: Array<File>,
82
acceptableMimeTypes: Array<string>
83
): Promise<Array<{file: File; result: string}>>;
84
```
85
86
**Usage Examples:**
87
88
```typescript
89
import { mediaFileReader } from "@lexical/utils";
90
91
// Basic image upload handling
92
async function handleImageUpload(files: File[]) {
93
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
94
95
try {
96
const results = await mediaFileReader(files, imageTypes);
97
98
results.forEach(({ file, result }) => {
99
console.log(`Processed ${file.name}: ${result.length} characters`);
100
101
// Insert into editor
102
editor.dispatchCommand('INSERT_IMAGE', {
103
src: result, // Data URL string
104
alt: file.name,
105
width: 'auto',
106
height: 'auto'
107
});
108
});
109
} catch (error) {
110
console.error('Failed to process images:', error);
111
}
112
}
113
114
// Multiple file type processing
115
async function handleMultipleFileTypes(files: File[]) {
116
// Process images
117
const imageResults = await mediaFileReader(files, ['image/']);
118
119
// Process videos
120
const videoResults = await mediaFileReader(files, ['video/']);
121
122
// Process audio
123
const audioResults = await mediaFileReader(files, ['audio/']);
124
125
// Handle each type differently
126
imageResults.forEach(({ file, result }) => {
127
insertImage(result, file.name);
128
});
129
130
videoResults.forEach(({ file, result }) => {
131
insertVideo(result, file.name);
132
});
133
134
audioResults.forEach(({ file, result }) => {
135
insertAudio(result, file.name);
136
});
137
}
138
139
// With progress tracking and error handling
140
async function handleFileUploadWithProgress(files: File[]) {
141
const acceptedTypes = ['image/jpeg', 'image/png', 'image/gif'];
142
143
// Filter files first to show immediate feedback
144
const validFiles = files.filter(file => isMimeType(file, acceptedTypes));
145
const invalidFiles = files.filter(file => !isMimeType(file, acceptedTypes));
146
147
if (invalidFiles.length > 0) {
148
console.warn(`Skipped ${invalidFiles.length} invalid files:`,
149
invalidFiles.map(f => f.name));
150
}
151
152
if (validFiles.length === 0) {
153
throw new Error('No valid image files to process');
154
}
155
156
// Show progress
157
console.log(`Processing ${validFiles.length} files...`);
158
159
const results = await mediaFileReader(validFiles, acceptedTypes);
160
161
// Results maintain original file order
162
results.forEach(({ file, result }, index) => {
163
console.log(`File ${index + 1}/${results.length}: ${file.name} processed`);
164
165
// Create image element
166
const img = new Image();
167
img.onload = () => {
168
console.log(`Image loaded: ${img.width}x${img.height}`);
169
};
170
img.src = result;
171
});
172
173
return results;
174
}
175
176
// Drag and drop integration
177
function setupDragAndDrop(editor: LexicalEditor) {
178
const dropZone = editor.getRootElement();
179
180
dropZone?.addEventListener('drop', async (event) => {
181
event.preventDefault();
182
183
const files = Array.from(event.dataTransfer?.files || []);
184
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
185
186
try {
187
const results = await mediaFileReader(files, imageTypes);
188
189
// Insert images at drop position
190
editor.update(() => {
191
results.forEach(({ file, result }) => {
192
const imageNode = createImageNode({
193
src: result,
194
alt: file.name
195
});
196
197
$insertNodeToNearestRoot(imageNode);
198
});
199
});
200
} catch (error) {
201
console.error('Drop upload failed:', error);
202
}
203
});
204
}
205
206
// Batch processing with size limits
207
async function handleLargeFileSet(files: File[], maxBatchSize: number = 5) {
208
const imageTypes = ['image/'];
209
const validFiles = files.filter(file => isMimeType(file, imageTypes));
210
211
const results: Array<{file: File; result: string}> = [];
212
213
// Process in batches to avoid memory issues
214
for (let i = 0; i < validFiles.length; i += maxBatchSize) {
215
const batch = validFiles.slice(i, i + maxBatchSize);
216
console.log(`Processing batch ${Math.floor(i / maxBatchSize) + 1}...`);
217
218
const batchResults = await mediaFileReader(batch, imageTypes);
219
results.push(...batchResults);
220
221
// Optional: Add delay between batches
222
if (i + maxBatchSize < validFiles.length) {
223
await new Promise(resolve => setTimeout(resolve, 100));
224
}
225
}
226
227
return results;
228
}
229
```
230
231
## Error Handling
232
233
Both functions handle errors gracefully:
234
235
- `isMimeType` returns `false` for invalid inputs rather than throwing
236
- `mediaFileReader` rejects the promise if file reading fails
237
- Files that don't match MIME types are silently skipped by `mediaFileReader`
238
239
**Common Error Scenarios:**
240
241
```typescript
242
import { mediaFileReader, isMimeType } from "@lexical/utils";
243
244
async function robustFileHandling(files: File[]) {
245
const acceptedTypes = ['image/jpeg', 'image/png'];
246
247
// Pre-validate files
248
const validationResults = files.map(file => ({
249
file,
250
valid: isMimeType(file, acceptedTypes),
251
error: !isMimeType(file, acceptedTypes) ?
252
`Invalid type: ${file.type}` : null
253
}));
254
255
const validFiles = validationResults
256
.filter(result => result.valid)
257
.map(result => result.file);
258
259
const errors = validationResults
260
.filter(result => !result.valid)
261
.map(result => result.error);
262
263
if (errors.length > 0) {
264
console.warn('File validation errors:', errors);
265
}
266
267
if (validFiles.length === 0) {
268
throw new Error('No valid files to process');
269
}
270
271
try {
272
const results = await mediaFileReader(validFiles, acceptedTypes);
273
return { results, errors };
274
} catch (readError) {
275
console.error('File reading failed:', readError);
276
throw new Error(`Failed to read files: ${readError.message}`);
277
}
278
}
279
```