This package provides essential utility functions for the Lexical rich text editor framework, offering a comprehensive set of DOM manipulation helpers, tree traversal algorithms, and editor state management tools.
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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.
Checks if a file matches one or more acceptable MIME types with case-sensitive comparison.
/**
* Returns true if the file type matches the types passed within the acceptableMimeTypes array, false otherwise.
* The types passed must be strings and are CASE-SENSITIVE.
* eg. if file is of type 'text' and acceptableMimeTypes = ['TEXT', 'IMAGE'] the function will return false.
* @param file - The file you want to type check.
* @param acceptableMimeTypes - An array of strings of types which the file is checked against.
* @returns true if the file is an acceptable mime type, false otherwise.
*/
function isMimeType(
file: File,
acceptableMimeTypes: Array<string>
): boolean;Usage Examples:
import { isMimeType } from "@lexical/utils";
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const files = Array.from(fileInput.files || []);
// Check for image files
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
const imageFiles = files.filter(file => isMimeType(file, imageTypes));
// Check for video files
const videoTypes = ['video/mp4', 'video/webm', 'video/quicktime'];
const videoFiles = files.filter(file => isMimeType(file, videoTypes));
// Check for text files (case-sensitive)
const textTypes = ['text/plain', 'text/markdown', 'text/html'];
const textFiles = files.filter(file => isMimeType(file, textTypes));
// Using MIME type prefixes for broader matching
const audioTypes = ['audio/']; // Matches any audio type
const audioFiles = files.filter(file => isMimeType(file, audioTypes));
// Validation with feedback
function validateFileType(file: File): { valid: boolean; message: string } {
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
if (isMimeType(file, allowedTypes)) {
return { valid: true, message: 'File type accepted' };
} else {
return {
valid: false,
message: `File type '${file.type}' not supported. Please upload JPEG, PNG, or PDF files.`
};
}
}Advanced asynchronous file reader with MIME type filtering, batched results, and order preservation for compatibility with Lexical's history system.
/**
* Lexical File Reader with:
* 1. MIME type support
* 2. batched results (HistoryPlugin compatibility)
* 3. Order aware (respects the order when multiple Files are passed)
*
* const filesResult = await mediaFileReader(files, ['image/']);
* filesResult.forEach(file => editor.dispatchCommand('INSERT_IMAGE', {
* src: file.result,
* }));
*/
function mediaFileReader(
files: Array<File>,
acceptableMimeTypes: Array<string>
): Promise<Array<{file: File; result: string}>>;Usage Examples:
import { mediaFileReader } from "@lexical/utils";
// Basic image upload handling
async function handleImageUpload(files: File[]) {
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
try {
const results = await mediaFileReader(files, imageTypes);
results.forEach(({ file, result }) => {
console.log(`Processed ${file.name}: ${result.length} characters`);
// Insert into editor
editor.dispatchCommand('INSERT_IMAGE', {
src: result, // Data URL string
alt: file.name,
width: 'auto',
height: 'auto'
});
});
} catch (error) {
console.error('Failed to process images:', error);
}
}
// Multiple file type processing
async function handleMultipleFileTypes(files: File[]) {
// Process images
const imageResults = await mediaFileReader(files, ['image/']);
// Process videos
const videoResults = await mediaFileReader(files, ['video/']);
// Process audio
const audioResults = await mediaFileReader(files, ['audio/']);
// Handle each type differently
imageResults.forEach(({ file, result }) => {
insertImage(result, file.name);
});
videoResults.forEach(({ file, result }) => {
insertVideo(result, file.name);
});
audioResults.forEach(({ file, result }) => {
insertAudio(result, file.name);
});
}
// With progress tracking and error handling
async function handleFileUploadWithProgress(files: File[]) {
const acceptedTypes = ['image/jpeg', 'image/png', 'image/gif'];
// Filter files first to show immediate feedback
const validFiles = files.filter(file => isMimeType(file, acceptedTypes));
const invalidFiles = files.filter(file => !isMimeType(file, acceptedTypes));
if (invalidFiles.length > 0) {
console.warn(`Skipped ${invalidFiles.length} invalid files:`,
invalidFiles.map(f => f.name));
}
if (validFiles.length === 0) {
throw new Error('No valid image files to process');
}
// Show progress
console.log(`Processing ${validFiles.length} files...`);
const results = await mediaFileReader(validFiles, acceptedTypes);
// Results maintain original file order
results.forEach(({ file, result }, index) => {
console.log(`File ${index + 1}/${results.length}: ${file.name} processed`);
// Create image element
const img = new Image();
img.onload = () => {
console.log(`Image loaded: ${img.width}x${img.height}`);
};
img.src = result;
});
return results;
}
// Drag and drop integration
function setupDragAndDrop(editor: LexicalEditor) {
const dropZone = editor.getRootElement();
dropZone?.addEventListener('drop', async (event) => {
event.preventDefault();
const files = Array.from(event.dataTransfer?.files || []);
const imageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
try {
const results = await mediaFileReader(files, imageTypes);
// Insert images at drop position
editor.update(() => {
results.forEach(({ file, result }) => {
const imageNode = createImageNode({
src: result,
alt: file.name
});
$insertNodeToNearestRoot(imageNode);
});
});
} catch (error) {
console.error('Drop upload failed:', error);
}
});
}
// Batch processing with size limits
async function handleLargeFileSet(files: File[], maxBatchSize: number = 5) {
const imageTypes = ['image/'];
const validFiles = files.filter(file => isMimeType(file, imageTypes));
const results: Array<{file: File; result: string}> = [];
// Process in batches to avoid memory issues
for (let i = 0; i < validFiles.length; i += maxBatchSize) {
const batch = validFiles.slice(i, i + maxBatchSize);
console.log(`Processing batch ${Math.floor(i / maxBatchSize) + 1}...`);
const batchResults = await mediaFileReader(batch, imageTypes);
results.push(...batchResults);
// Optional: Add delay between batches
if (i + maxBatchSize < validFiles.length) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
return results;
}Both functions handle errors gracefully:
isMimeType returns false for invalid inputs rather than throwingmediaFileReader rejects the promise if file reading failsmediaFileReaderCommon Error Scenarios:
import { mediaFileReader, isMimeType } from "@lexical/utils";
async function robustFileHandling(files: File[]) {
const acceptedTypes = ['image/jpeg', 'image/png'];
// Pre-validate files
const validationResults = files.map(file => ({
file,
valid: isMimeType(file, acceptedTypes),
error: !isMimeType(file, acceptedTypes) ?
`Invalid type: ${file.type}` : null
}));
const validFiles = validationResults
.filter(result => result.valid)
.map(result => result.file);
const errors = validationResults
.filter(result => !result.valid)
.map(result => result.error);
if (errors.length > 0) {
console.warn('File validation errors:', errors);
}
if (validFiles.length === 0) {
throw new Error('No valid files to process');
}
try {
const results = await mediaFileReader(validFiles, acceptedTypes);
return { results, errors };
} catch (readError) {
console.error('File reading failed:', readError);
throw new Error(`Failed to read files: ${readError.message}`);
}
}