Files
Zero/apps/mail/lib/schemas.ts
Ahmet Kilinc b60661e6a6 fix: add inline images (#1486)
# Add image support to email composer

This PR adds support for inline images in the email composer. Users can now drag and drop or paste images directly into the email body, which will be properly embedded in the outgoing emails.

## Key changes:

- Added TipTap extensions for image handling and file handling
- Modified the sanitize HTML function to process base64 images and convert them to CID references
- Updated Google and Microsoft mail managers to handle inline images in emails

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

* **New Features**
  * Added support for inserting and handling images (including drag-and-drop and paste) in the email composer.
  * Inline images are now properly embedded and displayed in sent emails and drafts.

* **Bug Fixes**
  * Improved sanitization to allow safe image embedding while restricting allowed tags and attributes.

* **Chores**
  * Updated and added dependencies to support image handling and improved email processing.
  * Removed the toast notification test route and related UI.

* **Refactor**
  * Streamlined attachment processing and schema handling for better reliability and maintainability.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-06-26 19:52:36 +01:00

59 lines
1.5 KiB
TypeScript

import { z } from 'zod';
export const serializedFileSchema = z.object({
name: z.string(),
type: z.string(),
size: z.number(),
lastModified: z.number(),
base64: z.string(),
});
export const serializeFiles = async (files: File[]) => {
return await Promise.all(
files.map(async (file) => {
const reader = new FileReader();
const base64 = await new Promise<string>((resolve) => {
reader.onloadend = () => {
const base64String = reader.result as string;
resolve(base64String.split(',')[1]!); // Remove the data URL prefix
};
reader.readAsDataURL(file);
});
return {
name: file.name,
type: file.type,
size: file.size,
lastModified: file.lastModified,
base64,
};
}),
);
};
export const deserializeFiles = async (serializedFiles: z.infer<typeof serializedFileSchema>[]) => {
return await Promise.all(
serializedFiles.map((data) => {
const file = Buffer.from(data.base64, 'base64');
const blob = new Blob([file], { type: data.type });
const newFile = new File([blob], data.name, {
type: data.type,
lastModified: data.lastModified,
});
return newFile;
}),
);
};
export const createDraftData = z.object({
to: z.string(),
cc: z.string().optional(),
bcc: z.string().optional(),
subject: z.string(),
message: z.string(),
attachments: z.array(serializedFileSchema).optional(),
id: z.string().nullable(),
});
export type CreateDraftData = z.infer<typeof createDraftData>;