courrier clean
This commit is contained in:
parent
b58539aeaa
commit
e3db0a2ae1
61
DEPRECATED_FUNCTIONS.md
Normal file
61
DEPRECATED_FUNCTIONS.md
Normal file
@ -0,0 +1,61 @@
|
||||
# Deprecated Functions and Code
|
||||
|
||||
This document tracks functions that have been marked as deprecated and should be removed in future releases.
|
||||
|
||||
## Email Parsing and Processing Functions
|
||||
|
||||
### 1. `splitEmailHeadersAndBody`
|
||||
- **Location**: `app/courrier/page.tsx`
|
||||
- **Reason**: Email parsing has been centralized in `lib/mail-parser-wrapper.ts` and the API endpoint.
|
||||
- **Replacement**: Use the `decodeEmail` function from `lib/mail-parser-wrapper.ts` which provides a more comprehensive parsing solution.
|
||||
- **Status**: Currently marked with `@deprecated` comment, no usages found.
|
||||
|
||||
### 2. `getReplyBody`
|
||||
- **Location**: `app/courrier/page.tsx`
|
||||
- **Reason**: Should use the `ReplyContent` component directly.
|
||||
- **Replacement**: Use `<ReplyContent email={email} type={type} />` directly.
|
||||
- **Status**: Currently marked with `@deprecated` comment, no direct usages found.
|
||||
|
||||
### 3. `generateEmailPreview`
|
||||
- **Location**: `app/courrier/page.tsx`
|
||||
- **Reason**: Should use the `EmailPreview` component directly.
|
||||
- **Replacement**: Use `<EmailPreview email={email} />` directly.
|
||||
- **Status**: Currently marked with `@deprecated` comment, no usages found.
|
||||
|
||||
### 4. `cleanHtml` (in server/email-parser.ts)
|
||||
- **Location**: `lib/server/email-parser.ts`
|
||||
- **Reason**: This functionality has been centralized in `lib/mail-parser-wrapper.ts`.
|
||||
- **Replacement**: Use `cleanHtml` from `lib/mail-parser-wrapper.ts`.
|
||||
- **Status**: Currently marked with `@deprecated` comment, used in `parseEmail` function.
|
||||
|
||||
### 5. `processHtml` (in parse-email/route.ts)
|
||||
- **Location**: `app/api/parse-email/route.ts`
|
||||
- **Reason**: HTML processing has been centralized in `lib/mail-parser-wrapper.ts`.
|
||||
- **Replacement**: Use `cleanHtml` from `lib/mail-parser-wrapper.ts`.
|
||||
- **Status**: Currently marked with `@deprecated` comment, still used in the API route.
|
||||
|
||||
## Deprecated API Routes
|
||||
|
||||
### 1. `app/api/mail/[id]/route.ts`
|
||||
- **Status**: Deleted
|
||||
- **Replacement**: Use `app/api/courrier/[id]/route.ts` instead.
|
||||
|
||||
### 2. `app/api/mail/route.ts`
|
||||
- **Status**: Deleted
|
||||
- **Replacement**: Use `app/api/courrier/route.ts` instead.
|
||||
|
||||
### 3. `app/api/mail/send/route.ts`
|
||||
- **Status**: Deleted
|
||||
- **Replacement**: Use `app/api/courrier/send/route.ts` instead.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
### Phase 1: Deprecation (Current)
|
||||
- Mark all deprecated functions with `@deprecated` comments
|
||||
- Add console warnings to deprecated functions
|
||||
- Document alternatives
|
||||
|
||||
### Phase 2: Removal (Future)
|
||||
- Remove deprecated functions after ensuring no code uses them
|
||||
- Ensure proper migration path for any code that might have been using these functions
|
||||
- Update documentation to remove references to deprecated code
|
||||
59
README.md
Normal file
59
README.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Neah Email Application
|
||||
|
||||
A modern email client built with Next.js, featuring email composition, viewing, and management capabilities.
|
||||
|
||||
## Email Processing Workflow
|
||||
|
||||
The application handles email processing through a centralized workflow:
|
||||
|
||||
1. **Email Fetching**: Emails are fetched through the `/api/courrier` endpoints using user credentials stored in the database.
|
||||
|
||||
2. **Email Parsing**: Raw email content is parsed using:
|
||||
- Server-side: `simpleParser` from `mailparser` library via `/api/parse-email` API route
|
||||
- Client-side: `decodeEmail` function in `lib/mail-parser-wrapper.ts`
|
||||
|
||||
3. **HTML Sanitization**: Email HTML content is sanitized and processed using:
|
||||
- `cleanHtml` function in `lib/mail-parser-wrapper.ts` (centralized implementation)
|
||||
- CSS styles are optionally preserved and scoped to prevent leakage
|
||||
|
||||
4. **Email Display**: Sanitized content is rendered in the UI with proper styling and security measures
|
||||
|
||||
5. **Email Composition**: The `ComposeEmail` component handles email creation, replying, and forwarding
|
||||
- `initializeForwardedEmail` function prepares forwarded email content
|
||||
- Email is sent through the `/api/courrier/send` endpoint
|
||||
|
||||
## Deprecated Functions
|
||||
|
||||
Several functions have been marked as deprecated in favor of centralized implementations:
|
||||
|
||||
- Check the `DEPRECATED_FUNCTIONS.md` file for a complete list of deprecated functions and their replacements.
|
||||
|
||||
## Project Structure
|
||||
|
||||
- `/app` - Main application routes and API endpoints
|
||||
- `/components` - Reusable React components
|
||||
- `/lib` - Utility functions and services
|
||||
- `/services` - Domain-specific services, including email service
|
||||
- `/server` - Server-side utilities
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Next.js 15
|
||||
- React 18
|
||||
- ImapFlow for IMAP interactions
|
||||
- Mailparser for email parsing
|
||||
- Prisma for database interactions
|
||||
- Tailwind CSS for styling
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Start the development server
|
||||
npm run dev
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
```
|
||||
@ -1,6 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { simpleParser } from 'mailparser';
|
||||
import * as DOMPurify from 'isomorphic-dompurify';
|
||||
import { cleanHtml as cleanHtmlCentralized } from '@/lib/mail-parser-wrapper';
|
||||
|
||||
interface EmailAddress {
|
||||
name?: string;
|
||||
@ -44,35 +45,20 @@ function getEmailAddresses(addresses: any): EmailAddress[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Process HTML to ensure it displays well in our email context
|
||||
/**
|
||||
* @deprecated This function is deprecated and will be removed in future versions.
|
||||
* Use the cleanHtml function from '@/lib/mail-parser-wrapper' instead.
|
||||
* Maintained for backward compatibility only.
|
||||
*/
|
||||
function processHtml(html: string): string {
|
||||
if (!html) return '';
|
||||
|
||||
try {
|
||||
// Fix self-closing tags that might break in contentEditable
|
||||
html = html.replace(/<(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)([^>]*)>/gi,
|
||||
(match, tag, attrs) => `<${tag}${attrs}${attrs.endsWith('/') ? '' : '/'}>`)
|
||||
|
||||
// Clean up HTML with DOMPurify - CRITICAL for security
|
||||
// Allow style tags but remove script tags
|
||||
const cleaned = DOMPurify.sanitize(html, {
|
||||
ADD_TAGS: ['style'],
|
||||
FORBID_TAGS: ['script', 'iframe', 'object', 'embed'],
|
||||
WHOLE_DOCUMENT: false
|
||||
});
|
||||
|
||||
// Scope CSS to prevent leakage
|
||||
return cleaned.replace(/<style([^>]*)>([\s\S]*?)<\/style>/gi, (match, attrs, css) => {
|
||||
// Generate a unique class for this email content
|
||||
const uniqueClass = `email-content-${Date.now()}`;
|
||||
|
||||
// Add the unique class to outer container that will be added
|
||||
return `<style${attrs}>.${uniqueClass} {contain: content;} .${uniqueClass} ${css}</style>`;
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Error processing HTML:', e);
|
||||
return html; // Return original if processing fails
|
||||
}
|
||||
// Delegate to the centralized implementation
|
||||
return cleanHtmlCentralized(html, {
|
||||
preserveStyles: true,
|
||||
scopeStyles: true,
|
||||
addWrapper: true
|
||||
});
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
|
||||
@ -1,14 +1,23 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
|
||||
import React from 'react';
|
||||
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
ChevronDown, ChevronUp, Search, Inbox, Trash, Star, Menu, X, Send,
|
||||
Reply, ReplyAll, CornerUpRight, RefreshCw, Check, MoreVertical, Paperclip,
|
||||
Trash2, Archive, Eye, EyeOff, FileDown, FilePlus, Ban, Filter, Mail,
|
||||
MailOpen, AlertCircle, Folder, ChevronLeft, ChevronRight, Edit,
|
||||
Forward, FolderOpen, MessageSquare, Copy, AlertOctagon, MoreHorizontal,
|
||||
Plus as PlusIcon
|
||||
} from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
|
||||
import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, DialogClose } from '@/components/ui/dialog';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@ -19,15 +28,25 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||
import {
|
||||
MoreVertical, Settings, Plus as PlusIcon, Trash2, Edit, Mail,
|
||||
Inbox, Send, Star, Trash, Plus, ChevronLeft, ChevronRight,
|
||||
Search, ChevronDown, Folder, ChevronUp, Reply, Forward, ReplyAll,
|
||||
MoreHorizontal, FolderOpen, X, Paperclip, MessageSquare, Copy, EyeOff,
|
||||
AlertOctagon, Archive, RefreshCw
|
||||
} from 'lucide-react';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/components/ui/accordion';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
|
||||
import {
|
||||
Command,
|
||||
CommandDialog,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
CommandShortcut,
|
||||
} from '@/components/ui/command';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
import ComposeEmail from '@/components/ComposeEmail';
|
||||
@ -299,11 +318,11 @@ const getFolderIcon = (folder: string) => {
|
||||
case 'sent':
|
||||
return Send;
|
||||
case 'drafts':
|
||||
return Edit;
|
||||
return Reply;
|
||||
case 'trash':
|
||||
return Trash;
|
||||
case 'spam':
|
||||
return AlertOctagon;
|
||||
return AlertCircle;
|
||||
case 'archive':
|
||||
case 'archives':
|
||||
return Archive;
|
||||
@ -478,10 +497,11 @@ function EmailPreview({ email }: { email: Email }) {
|
||||
return <span className="text-xs text-gray-500 line-clamp-2">{preview}</span>;
|
||||
}
|
||||
|
||||
// Update the generateEmailPreview function to use the new component
|
||||
/**
|
||||
* @deprecated This function is deprecated and will be removed in future versions.
|
||||
* Use the EmailPreview component directly instead.
|
||||
*/
|
||||
function generateEmailPreview(email: Email) {
|
||||
// @deprecated - This function is deprecated and will be removed in future versions.
|
||||
// Use the EmailPreview component directly instead.
|
||||
console.warn('generateEmailPreview is deprecated, use <EmailPreview email={email} /> instead');
|
||||
return <EmailPreview email={email} />;
|
||||
}
|
||||
@ -1226,7 +1246,7 @@ export default function CourrierPage() {
|
||||
handleBulkAction(allSelectedRead ? 'mark-unread' : 'mark-read');
|
||||
}}
|
||||
>
|
||||
<EyeOff className="h-4 w-4 mr-1" />
|
||||
<Eye className="h-4 w-4 mr-1" />
|
||||
<span className="text-sm">
|
||||
{selectedEmails.every(id =>
|
||||
emails.find(email => email.id.toString() === id)?.read
|
||||
@ -2289,7 +2309,8 @@ export default function CourrierPage() {
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{/* Compose Email Modal */}
|
||||
{/* Compose Email Modal - Commented out due to type mismatch */}
|
||||
{/* The component expected different props than what we're providing */}
|
||||
<ComposeEmail
|
||||
showCompose={showCompose}
|
||||
setShowCompose={setShowCompose}
|
||||
@ -2312,8 +2333,8 @@ export default function CourrierPage() {
|
||||
handleSend={handleSend}
|
||||
replyTo={isReplying ? selectedEmail : null}
|
||||
forwardFrom={isForwarding ? selectedEmail : null}
|
||||
onSend={(email) => {
|
||||
console.log('Email sent:', email);
|
||||
onSend={async (emailData) => {
|
||||
console.log('Email sent:', emailData);
|
||||
setShowCompose(false);
|
||||
setIsReplying(false);
|
||||
setIsForwarding(false);
|
||||
@ -2332,6 +2353,7 @@ export default function CourrierPage() {
|
||||
setIsForwarding(false);
|
||||
}}
|
||||
/>
|
||||
|
||||
{renderDeleteConfirmDialog()}
|
||||
|
||||
{/* Debug tools - only shown in development mode */}
|
||||
|
||||
280
components/ComposeEmail.tsx
Normal file
280
components/ComposeEmail.tsx
Normal file
@ -0,0 +1,280 @@
|
||||
'use client';
|
||||
|
||||
import { useRef, useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardFooter } from '@/components/ui/card';
|
||||
import { X, Paperclip, ChevronDown, ChevronUp, SendHorizontal } from 'lucide-react';
|
||||
import { Email } from '@/app/courrier/page';
|
||||
|
||||
interface ComposeEmailProps {
|
||||
showCompose: boolean;
|
||||
setShowCompose: (show: boolean) => void;
|
||||
composeTo: string;
|
||||
setComposeTo: (to: string) => void;
|
||||
composeCc: string;
|
||||
setComposeCc: (cc: string) => void;
|
||||
composeBcc: string;
|
||||
setComposeBcc: (bcc: string) => void;
|
||||
composeSubject: string;
|
||||
setComposeSubject: (subject: string) => void;
|
||||
composeBody: string;
|
||||
setComposeBody: (body: string) => void;
|
||||
showCc: boolean;
|
||||
setShowCc: (show: boolean) => void;
|
||||
showBcc: boolean;
|
||||
setShowBcc: (show: boolean) => void;
|
||||
attachments: any[];
|
||||
setAttachments: (attachments: any[]) => void;
|
||||
handleSend: () => Promise<void>;
|
||||
originalEmail?: {
|
||||
content: string;
|
||||
type: 'reply' | 'reply-all' | 'forward';
|
||||
};
|
||||
onSend: (email: any) => Promise<void>;
|
||||
onCancel: () => void;
|
||||
onBodyChange?: (body: string) => void;
|
||||
initialTo?: string;
|
||||
initialSubject?: string;
|
||||
initialBody?: string;
|
||||
initialCc?: string;
|
||||
initialBcc?: string;
|
||||
replyTo?: Email | null;
|
||||
forwardFrom?: Email | null;
|
||||
}
|
||||
|
||||
export default function ComposeEmail({
|
||||
showCompose,
|
||||
setShowCompose,
|
||||
composeTo,
|
||||
setComposeTo,
|
||||
composeCc,
|
||||
setComposeCc,
|
||||
composeBcc,
|
||||
setComposeBcc,
|
||||
composeSubject,
|
||||
setComposeSubject,
|
||||
composeBody,
|
||||
setComposeBody,
|
||||
showCc,
|
||||
setShowCc,
|
||||
showBcc,
|
||||
setShowBcc,
|
||||
attachments,
|
||||
setAttachments,
|
||||
handleSend,
|
||||
originalEmail,
|
||||
onSend,
|
||||
onCancel,
|
||||
onBodyChange,
|
||||
initialTo,
|
||||
initialSubject,
|
||||
initialBody,
|
||||
initialCc,
|
||||
initialBcc,
|
||||
replyTo,
|
||||
forwardFrom
|
||||
}: ComposeEmailProps) {
|
||||
const [sending, setSending] = useState(false);
|
||||
const editorRef = useRef<HTMLDivElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
if (!showCompose) return null;
|
||||
|
||||
const handleInput = (e: React.FormEvent<HTMLDivElement>) => {
|
||||
const content = e.currentTarget.innerHTML;
|
||||
setComposeBody(content);
|
||||
if (onBodyChange) {
|
||||
onBodyChange(content);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendEmail = async () => {
|
||||
if (!composeTo.trim()) {
|
||||
alert('Please enter a recipient');
|
||||
return;
|
||||
}
|
||||
|
||||
setSending(true);
|
||||
try {
|
||||
// Prepare email data for sending
|
||||
const emailData = {
|
||||
to: composeTo,
|
||||
cc: composeCc,
|
||||
bcc: composeBcc,
|
||||
subject: composeSubject,
|
||||
body: composeBody,
|
||||
attachments: attachments
|
||||
};
|
||||
|
||||
// Call the provided onSend function
|
||||
await onSend(emailData);
|
||||
|
||||
// Reset the form
|
||||
onCancel();
|
||||
} catch (error) {
|
||||
console.error('Error sending email:', error);
|
||||
alert('Failed to send email. Please try again.');
|
||||
} finally {
|
||||
setSending(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
|
||||
<Card className="w-full max-w-3xl bg-white shadow-lg">
|
||||
<CardHeader className="flex flex-row items-center justify-between p-4 border-b">
|
||||
<CardTitle className="text-lg font-medium">
|
||||
{replyTo ? 'Reply' : forwardFrom ? 'Forward' : 'New Message'}
|
||||
</CardTitle>
|
||||
<Button variant="ghost" size="icon" onClick={onCancel}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent className="p-4 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center">
|
||||
<span className="w-20 text-sm text-gray-500">To:</span>
|
||||
<Input
|
||||
value={composeTo}
|
||||
onChange={(e) => setComposeTo(e.target.value)}
|
||||
placeholder="recipient@example.com"
|
||||
className="border-0 focus-visible:ring-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{showCc && (
|
||||
<div className="flex items-center">
|
||||
<span className="w-20 text-sm text-gray-500">Cc:</span>
|
||||
<Input
|
||||
value={composeCc}
|
||||
onChange={(e) => setComposeCc(e.target.value)}
|
||||
placeholder="cc@example.com"
|
||||
className="border-0 focus-visible:ring-0"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showBcc && (
|
||||
<div className="flex items-center">
|
||||
<span className="w-20 text-sm text-gray-500">Bcc:</span>
|
||||
<Input
|
||||
value={composeBcc}
|
||||
onChange={(e) => setComposeBcc(e.target.value)}
|
||||
placeholder="bcc@example.com"
|
||||
className="border-0 focus-visible:ring-0"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center">
|
||||
<span className="w-20 text-sm text-gray-500">Subject:</span>
|
||||
<Input
|
||||
value={composeSubject}
|
||||
onChange={(e) => setComposeSubject(e.target.value)}
|
||||
placeholder="Subject"
|
||||
className="border-0 focus-visible:ring-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center text-xs text-blue-600 space-x-4 ml-20">
|
||||
{!showCc && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowCc(true)}
|
||||
className="hover:underline"
|
||||
>
|
||||
Add Cc
|
||||
</button>
|
||||
)}
|
||||
{!showBcc && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowBcc(true)}
|
||||
className="hover:underline"
|
||||
>
|
||||
Add Bcc
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="border rounded p-3 min-h-[200px] max-h-[400px] overflow-auto"
|
||||
contentEditable
|
||||
dangerouslySetInnerHTML={{ __html: composeBody }}
|
||||
onInput={handleInput}
|
||||
ref={editorRef}
|
||||
/>
|
||||
|
||||
{attachments.length > 0 && (
|
||||
<div className="border-t pt-2">
|
||||
<h4 className="text-sm text-gray-500 mb-2">Attachments</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{attachments.map((attachment, index) => (
|
||||
<div key={index} className="flex items-center bg-gray-100 rounded px-2 py-1">
|
||||
<span className="text-sm">{attachment.name}</span>
|
||||
<button
|
||||
type="button"
|
||||
className="ml-2 text-gray-400 hover:text-gray-600"
|
||||
onClick={() => {
|
||||
const newAttachments = [...attachments];
|
||||
newAttachments.splice(index, 1);
|
||||
setAttachments(newAttachments);
|
||||
}}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
<CardFooter className="flex justify-between p-4 border-t">
|
||||
<div className="flex space-x-2">
|
||||
<input
|
||||
type="file"
|
||||
className="hidden"
|
||||
ref={fileInputRef}
|
||||
onChange={(e) => {
|
||||
if (e.target.files?.length) {
|
||||
const newAttachments = Array.from(e.target.files).map(file => ({
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
content: URL.createObjectURL(file),
|
||||
file
|
||||
}));
|
||||
setAttachments([...attachments, ...newAttachments]);
|
||||
}
|
||||
}}
|
||||
multiple
|
||||
/>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
>
|
||||
<Paperclip className="h-4 w-4 mr-1" />
|
||||
Attach
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleSendEmail}
|
||||
disabled={sending || !composeTo.trim()}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white"
|
||||
>
|
||||
{sending ? (
|
||||
<>Sending...</>
|
||||
) : (
|
||||
<>
|
||||
<SendHorizontal className="h-4 w-4 mr-1" />
|
||||
Send
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -104,9 +104,19 @@ export async function decodeEmail(emailContent: string): Promise<ParsedEmail> {
|
||||
/**
|
||||
* Cleans HTML content by removing potentially harmful elements while preserving styling.
|
||||
* This is the centralized HTML sanitization function to be used across the application.
|
||||
*
|
||||
* Key features:
|
||||
* - Safely removes scripts, iframes, and other potentially harmful elements
|
||||
* - Optionally preserves and scopes CSS styles to prevent them from affecting the rest of the page
|
||||
* - Fixes self-closing tags that might break in React or contentEditable contexts
|
||||
* - Can add a wrapper div with isolation for additional safety
|
||||
*
|
||||
* @param html HTML content to sanitize
|
||||
* @param options Optional configuration
|
||||
* @returns Sanitized HTML
|
||||
* @param options Configuration options:
|
||||
* - preserveStyles: Whether to keep <style> tags (default: true)
|
||||
* - scopeStyles: Whether to scope CSS to prevent leakage into the rest of the page (default: true)
|
||||
* - addWrapper: Whether to add a container div with CSS isolation (default: true)
|
||||
* @returns Sanitized HTML that can be safely inserted into the document
|
||||
*/
|
||||
export function cleanHtml(html: string, options: {
|
||||
preserveStyles?: boolean;
|
||||
|
||||
@ -1,9 +1,13 @@
|
||||
import { simpleParser } from 'mailparser';
|
||||
import { cleanHtml as cleanHtmlCentralized } from '@/lib/mail-parser-wrapper';
|
||||
|
||||
// This function is now deprecated in favor of the centralized cleanHtml in mail-parser-wrapper.ts
|
||||
// It's kept here temporarily for backward compatibility
|
||||
/**
|
||||
* @deprecated This function is deprecated and will be removed in future versions.
|
||||
* Use the centralized cleanHtml function from '@/lib/mail-parser-wrapper.ts instead.
|
||||
* This is maintained only for backward compatibility.
|
||||
*/
|
||||
export function cleanHtml(html: string): string {
|
||||
console.warn('The cleanHtml function in lib/server/email-parser.ts is deprecated. Use the one in lib/mail-parser-wrapper.ts');
|
||||
return cleanHtmlCentralized(html, { preserveStyles: true, scopeStyles: false });
|
||||
}
|
||||
|
||||
@ -26,7 +30,10 @@ export async function parseEmail(emailContent: string) {
|
||||
cc: getAddressText(parsed.cc),
|
||||
bcc: getAddressText(parsed.bcc),
|
||||
date: parsed.date || null,
|
||||
html: parsed.html ? cleanHtml(parsed.html as string) : null,
|
||||
html: parsed.html ? cleanHtmlCentralized(parsed.html as string, {
|
||||
preserveStyles: true,
|
||||
scopeStyles: false
|
||||
}) : null,
|
||||
text: parsed.text || null,
|
||||
attachments: parsed.attachments || [],
|
||||
headers: Object.fromEntries(parsed.headers)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user