or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

dom-manipulation.mdeditor-state.mdfile-handling.mdindex.mdspecialized-utilities.mdtree-traversal.md
tile.json

file-handling.mddocs/

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

```