mirror of
https://github.com/Gowtham-Darkseid/MailComposer.git
synced 2026-04-15 16:08:27 +02:00
44ea4d2e64
- Rich email composer with formatting tools (bold, italic, underline, links) - Multi-recipient support (To, CC, BCC fields) - File attachments with drag & drop support - Priority levels and email validation - Draft management with auto-save and cloud sync - Real email sending via EmailJS integration - Responsive design for mobile and desktop - Comprehensive error handling and fallback modes - Complete documentation and setup guides - Firebase integration ready for advanced features Features: ✅ Real email sending (EmailJS) ✅ Rich text editor ✅ File attachments ✅ Draft management ✅ Form validation ✅ Responsive UI ✅ Error handling ✅ Documentation
677 lines
21 KiB
JavaScript
677 lines
21 KiB
JavaScript
// Import Firebase functions
|
||
import { sendEmail } from './firebase-config.js';
|
||
import { collection, addDoc, getDocs, deleteDoc, doc } from 'firebase/firestore';
|
||
import { db } from './firebase-config.js';
|
||
|
||
// Import EmailJS as simpler alternative
|
||
import { initEmailJS, sendEmailViaEmailJS, isEmailJSConfigured, testEmailJS } from './emailjs-config.js';
|
||
|
||
// Check if Firebase is properly configured
|
||
const isFirebaseConfigured = () => {
|
||
try {
|
||
return import.meta.env.VITE_FIREBASE_PROJECT_ID &&
|
||
import.meta.env.VITE_FIREBASE_PROJECT_ID !== "your-project-id";
|
||
} catch {
|
||
return false;
|
||
}
|
||
};
|
||
|
||
// Mail Composer Application
|
||
class MailComposer {
|
||
constructor() {
|
||
this.initializeElements();
|
||
this.attachEventListeners();
|
||
this.loadDrafts();
|
||
this.initializeEditor();
|
||
|
||
// Initialize EmailJS
|
||
initEmailJS();
|
||
|
||
// Update service status
|
||
this.updateServiceStatus();
|
||
}
|
||
|
||
initializeElements() {
|
||
this.form = document.getElementById('mailForm');
|
||
this.toField = document.getElementById('to');
|
||
this.ccField = document.getElementById('cc');
|
||
this.bccField = document.getElementById('bcc');
|
||
this.subjectField = document.getElementById('subject');
|
||
this.priorityField = document.getElementById('priority');
|
||
this.messageEditor = document.getElementById('message');
|
||
this.attachmentsField = document.getElementById('attachments');
|
||
this.attachmentList = document.getElementById('attachmentList');
|
||
this.sendBtn = document.getElementById('sendBtn');
|
||
this.saveDraftBtn = document.getElementById('saveDraftBtn');
|
||
this.clearBtn = document.getElementById('clearBtn');
|
||
this.testEmailJSBtn = document.getElementById('testEmailJSBtn');
|
||
this.messageStatus = document.getElementById('messageStatus');
|
||
this.draftsList = document.getElementById('draftsList');
|
||
this.charCount = document.getElementById('charCount');
|
||
this.emailServiceStatus = document.getElementById('emailServiceStatus');
|
||
|
||
// Toolbar buttons
|
||
this.boldBtn = document.getElementById('boldBtn');
|
||
this.italicBtn = document.getElementById('italicBtn');
|
||
this.underlineBtn = document.getElementById('underlineBtn');
|
||
this.linkBtn = document.getElementById('linkBtn');
|
||
|
||
this.attachments = [];
|
||
this.drafts = [];
|
||
}
|
||
|
||
updateServiceStatus() {
|
||
const statusElement = this.emailServiceStatus.querySelector('.status-indicator');
|
||
|
||
if (isEmailJSConfigured()) {
|
||
statusElement.textContent = '✅ EmailJS Active';
|
||
statusElement.className = 'status-indicator emailjs';
|
||
this.testEmailJSBtn.style.display = 'inline-block'; // Show test button
|
||
} else if (isFirebaseConfigured()) {
|
||
statusElement.textContent = '🔥 Firebase Active';
|
||
statusElement.className = 'status-indicator firebase';
|
||
this.testEmailJSBtn.style.display = 'none';
|
||
} else {
|
||
statusElement.textContent = '🔄 Demo Mode - Setup EmailJS for real sending';
|
||
statusElement.className = 'status-indicator demo';
|
||
this.testEmailJSBtn.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
async testEmailJSConnection() {
|
||
this.showMessage('🧪 Testing EmailJS connection...', 'warning');
|
||
|
||
try {
|
||
const result = await testEmailJS();
|
||
if (result.success) {
|
||
this.showMessage('✅ EmailJS test successful! Configuration is working.', 'success');
|
||
} else {
|
||
this.showMessage(`❌ EmailJS test failed: ${result.error}`, 'error');
|
||
}
|
||
} catch (error) {
|
||
this.showMessage(`❌ EmailJS test error: ${error.message}`, 'error');
|
||
}
|
||
}
|
||
|
||
attachEventListeners() {
|
||
// Form submission
|
||
this.form.addEventListener('submit', (e) => this.handleSubmit(e));
|
||
|
||
// Button events
|
||
this.saveDraftBtn.addEventListener('click', () => this.saveDraft());
|
||
this.clearBtn.addEventListener('click', () => this.clearForm());
|
||
this.testEmailJSBtn.addEventListener('click', () => this.testEmailJSConnection());
|
||
|
||
// File upload
|
||
this.attachmentsField.addEventListener('change', (e) => this.handleFileUpload(e));
|
||
|
||
// Message editor events
|
||
this.messageEditor.addEventListener('input', () => this.updateCharCount());
|
||
this.messageEditor.addEventListener('keydown', (e) => this.handleEditorKeydown(e));
|
||
|
||
// Toolbar events
|
||
this.boldBtn.addEventListener('click', () => this.formatText('bold'));
|
||
this.italicBtn.addEventListener('click', () => this.formatText('italic'));
|
||
this.underlineBtn.addEventListener('click', () => this.formatText('underline'));
|
||
this.linkBtn.addEventListener('click', () => this.insertLink());
|
||
|
||
// Auto-save draft every 30 seconds
|
||
setInterval(() => this.autoSaveDraft(), 30000);
|
||
}
|
||
|
||
initializeEditor() {
|
||
// Set up the rich text editor
|
||
this.messageEditor.addEventListener('mouseup', () => this.updateToolbarState());
|
||
this.messageEditor.addEventListener('keyup', () => this.updateToolbarState());
|
||
}
|
||
|
||
handleSubmit(e) {
|
||
e.preventDefault();
|
||
|
||
if (!this.validateForm()) {
|
||
return;
|
||
}
|
||
|
||
this.sendEmail();
|
||
}
|
||
|
||
validateForm() {
|
||
const to = this.toField.value.trim();
|
||
const subject = this.subjectField.value.trim();
|
||
const message = this.messageEditor.textContent.trim();
|
||
|
||
if (!to) {
|
||
this.showMessage('Please enter at least one recipient email address.', 'error');
|
||
this.toField.focus();
|
||
return false;
|
||
}
|
||
|
||
if (!this.validateEmails(to)) {
|
||
this.showMessage('Please enter valid email addresses in the "To" field.', 'error');
|
||
this.toField.focus();
|
||
return false;
|
||
}
|
||
|
||
if (!subject) {
|
||
this.showMessage('Please enter a subject for your email.', 'error');
|
||
this.subjectField.focus();
|
||
return false;
|
||
}
|
||
|
||
if (!message) {
|
||
this.showMessage('Please enter a message body.', 'error');
|
||
this.messageEditor.focus();
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
validateEmails(emailString) {
|
||
const emails = emailString.split(',').map(email => email.trim());
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
|
||
return emails.every(email => emailRegex.test(email));
|
||
}
|
||
|
||
async sendEmail() {
|
||
this.setLoading(true);
|
||
|
||
try {
|
||
const emailData = {
|
||
to: this.toField.value.split(',').map(email => email.trim()),
|
||
cc: this.ccField.value ? this.ccField.value.split(',').map(email => email.trim()) : [],
|
||
bcc: this.bccField.value ? this.bccField.value.split(',').map(email => email.trim()) : [],
|
||
subject: this.subjectField.value,
|
||
message: this.messageEditor.innerHTML,
|
||
priority: this.priorityField.value,
|
||
attachments: this.attachments.map(file => ({
|
||
name: file.name,
|
||
size: file.size,
|
||
type: file.type
|
||
}))
|
||
};
|
||
|
||
// Try EmailJS first (simpler setup)
|
||
if (isEmailJSConfigured()) {
|
||
const result = await sendEmailViaEmailJS(emailData);
|
||
this.showMessage('✅ Email sent successfully via EmailJS!', 'success');
|
||
this.clearForm();
|
||
this.removeDraftIfExists();
|
||
return;
|
||
}
|
||
|
||
// Fallback to Firebase if configured
|
||
if (isFirebaseConfigured()) {
|
||
const result = await sendEmail(emailData);
|
||
|
||
if (result.data.success) {
|
||
this.showMessage('✅ Email sent successfully via Firebase!', 'success');
|
||
this.clearForm();
|
||
this.removeDraftIfExists();
|
||
await this.logSentEmail(emailData);
|
||
} else {
|
||
throw new Error(result.data.error || 'Failed to send email');
|
||
}
|
||
return;
|
||
}
|
||
|
||
// Demo mode if neither service is configured
|
||
await this.simulateEmailSending();
|
||
this.showMessage('📧 Demo mode: Email would be sent! Configure EmailJS or Firebase for real sending.', 'warning');
|
||
this.clearForm();
|
||
this.removeDraftIfExists();
|
||
|
||
} catch (error) {
|
||
console.error('Error sending email:', error);
|
||
|
||
// Provide more specific error messages
|
||
if (error.message.includes('EmailJS')) {
|
||
this.showMessage(`❌ EmailJS Error: ${error.message}`, 'error');
|
||
} else if (error.code === 'functions/unauthenticated') {
|
||
this.showMessage('❌ Authentication required. Please check your Firebase configuration.', 'error');
|
||
} else if (error.code === 'functions/permission-denied') {
|
||
this.showMessage('❌ Permission denied. Please check your Firebase security rules.', 'error');
|
||
} else if (error.code === 'functions/unavailable') {
|
||
this.showMessage('❌ Email service temporarily unavailable. Please try again later.', 'error');
|
||
} else {
|
||
this.showMessage(`❌ Failed to send email: ${error.message}`, 'error');
|
||
}
|
||
} finally {
|
||
this.setLoading(false);
|
||
}
|
||
}
|
||
|
||
async simulateEmailSending() {
|
||
// Simulate network delay for demo mode
|
||
return new Promise((resolve) => {
|
||
setTimeout(() => {
|
||
resolve();
|
||
}, 2000);
|
||
});
|
||
}
|
||
|
||
async logSentEmail(emailData) {
|
||
try {
|
||
await addDoc(collection(db, 'sentEmails'), {
|
||
...emailData,
|
||
timestamp: new Date(),
|
||
status: 'sent'
|
||
});
|
||
} catch (error) {
|
||
console.warn('Failed to log sent email:', error);
|
||
}
|
||
}
|
||
|
||
async simulateEmailSending() {
|
||
// Simulate network delay
|
||
return new Promise((resolve, reject) => {
|
||
setTimeout(() => {
|
||
// Simulate 90% success rate
|
||
if (Math.random() > 0.1) {
|
||
resolve();
|
||
} else {
|
||
reject(new Error('Network error'));
|
||
}
|
||
}, 2000);
|
||
});
|
||
}
|
||
|
||
setLoading(isLoading) {
|
||
this.sendBtn.disabled = isLoading;
|
||
this.sendBtn.classList.toggle('loading', isLoading);
|
||
}
|
||
|
||
formatText(command) {
|
||
document.execCommand(command, false, null);
|
||
this.messageEditor.focus();
|
||
this.updateToolbarState();
|
||
}
|
||
|
||
insertLink() {
|
||
const url = prompt('Enter the URL:');
|
||
if (url) {
|
||
const selection = window.getSelection();
|
||
if (selection.rangeCount > 0) {
|
||
const range = selection.getRangeAt(0);
|
||
const selectedText = range.toString();
|
||
const linkText = selectedText || url;
|
||
|
||
const link = document.createElement('a');
|
||
link.href = url;
|
||
link.textContent = linkText;
|
||
link.target = '_blank';
|
||
|
||
range.deleteContents();
|
||
range.insertNode(link);
|
||
|
||
// Move cursor after the link
|
||
range.setStartAfter(link);
|
||
range.collapse(true);
|
||
selection.removeAllRanges();
|
||
selection.addRange(range);
|
||
}
|
||
}
|
||
this.messageEditor.focus();
|
||
}
|
||
|
||
updateToolbarState() {
|
||
this.boldBtn.classList.toggle('active', document.queryCommandState('bold'));
|
||
this.italicBtn.classList.toggle('active', document.queryCommandState('italic'));
|
||
this.underlineBtn.classList.toggle('active', document.queryCommandState('underline'));
|
||
}
|
||
|
||
handleEditorKeydown(e) {
|
||
// Handle keyboard shortcuts
|
||
if (e.ctrlKey || e.metaKey) {
|
||
switch(e.key) {
|
||
case 'b':
|
||
e.preventDefault();
|
||
this.formatText('bold');
|
||
break;
|
||
case 'i':
|
||
e.preventDefault();
|
||
this.formatText('italic');
|
||
break;
|
||
case 'u':
|
||
e.preventDefault();
|
||
this.formatText('underline');
|
||
break;
|
||
case 's':
|
||
e.preventDefault();
|
||
this.saveDraft();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
updateCharCount() {
|
||
const count = this.messageEditor.textContent.length;
|
||
this.charCount.textContent = count.toLocaleString();
|
||
}
|
||
|
||
handleFileUpload(e) {
|
||
const files = Array.from(e.target.files);
|
||
|
||
files.forEach(file => {
|
||
if (this.validateFile(file)) {
|
||
this.attachments.push(file);
|
||
this.addAttachmentToList(file);
|
||
}
|
||
});
|
||
|
||
// Clear the input so the same file can be selected again
|
||
e.target.value = '';
|
||
}
|
||
|
||
validateFile(file) {
|
||
const maxSize = 10 * 1024 * 1024; // 10MB
|
||
const allowedTypes = [
|
||
'application/pdf',
|
||
'application/msword',
|
||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||
'text/plain',
|
||
'image/jpeg',
|
||
'image/png',
|
||
'image/gif'
|
||
];
|
||
|
||
if (file.size > maxSize) {
|
||
this.showMessage(`File "${file.name}" is too large. Maximum size is 10MB.`, 'warning');
|
||
return false;
|
||
}
|
||
|
||
if (!allowedTypes.includes(file.type)) {
|
||
this.showMessage(`File type "${file.type}" is not allowed.`, 'warning');
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
addAttachmentToList(file) {
|
||
const attachmentItem = document.createElement('div');
|
||
attachmentItem.className = 'attachment-item';
|
||
attachmentItem.innerHTML = `
|
||
<span>📎 ${file.name} (${this.formatFileSize(file.size)})</span>
|
||
<button type="button" class="attachment-remove" data-filename="${file.name}">×</button>
|
||
`;
|
||
|
||
attachmentItem.querySelector('.attachment-remove').addEventListener('click', (e) => {
|
||
this.removeAttachment(file.name);
|
||
attachmentItem.remove();
|
||
});
|
||
|
||
this.attachmentList.appendChild(attachmentItem);
|
||
}
|
||
|
||
removeAttachment(filename) {
|
||
this.attachments = this.attachments.filter(file => file.name !== filename);
|
||
}
|
||
|
||
formatFileSize(bytes) {
|
||
if (bytes === 0) return '0 Bytes';
|
||
const k = 1024;
|
||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||
}
|
||
|
||
async saveDraft() {
|
||
const draft = this.getDraftData();
|
||
|
||
if (!draft.to && !draft.subject && !draft.message) {
|
||
this.showMessage('Nothing to save as draft.', 'warning');
|
||
return;
|
||
}
|
||
|
||
draft.id = Date.now().toString();
|
||
draft.timestamp = new Date().toISOString();
|
||
|
||
try {
|
||
// Save to Firebase Firestore if configured
|
||
if (isFirebaseConfigured()) {
|
||
await addDoc(collection(db, 'drafts'), draft);
|
||
this.showMessage('📝 Draft saved to cloud!', 'success');
|
||
} else {
|
||
this.showMessage('📝 Draft saved locally! (Configure Firebase for cloud sync)', 'warning');
|
||
}
|
||
|
||
// Also save locally as backup
|
||
this.drafts.push(draft);
|
||
this.saveDraftsToStorage();
|
||
this.renderDrafts();
|
||
|
||
} catch (error) {
|
||
console.warn('Failed to save draft to Firebase, saving locally:', error);
|
||
|
||
// Fallback to local storage
|
||
this.drafts.push(draft);
|
||
this.saveDraftsToStorage();
|
||
this.renderDrafts();
|
||
|
||
this.showMessage('📝 Draft saved locally!', 'warning');
|
||
}
|
||
}
|
||
|
||
async loadDraftsFromFirebase() {
|
||
if (!isFirebaseConfigured()) {
|
||
return; // Skip Firebase loading if not configured
|
||
}
|
||
|
||
try {
|
||
const querySnapshot = await getDocs(collection(db, 'drafts'));
|
||
const firebaseDrafts = [];
|
||
|
||
querySnapshot.forEach((doc) => {
|
||
firebaseDrafts.push({
|
||
firebaseId: doc.id,
|
||
...doc.data()
|
||
});
|
||
});
|
||
|
||
// Merge with local drafts
|
||
this.drafts = [...firebaseDrafts, ...this.drafts.filter(draft => !draft.firebaseId)];
|
||
this.renderDrafts();
|
||
|
||
} catch (error) {
|
||
console.warn('Failed to load drafts from Firebase:', error);
|
||
}
|
||
}
|
||
|
||
async deleteDraftFromFirebase(draft) {
|
||
if (draft.firebaseId) {
|
||
try {
|
||
await deleteDoc(doc(db, 'drafts', draft.firebaseId));
|
||
} catch (error) {
|
||
console.warn('Failed to delete draft from Firebase:', error);
|
||
}
|
||
}
|
||
}
|
||
|
||
autoSaveDraft() {
|
||
const draft = this.getDraftData();
|
||
|
||
if (draft.to || draft.subject || draft.message) {
|
||
// Check if there's already an auto-saved draft
|
||
const existingDraftIndex = this.drafts.findIndex(d => d.isAutoSave);
|
||
|
||
draft.id = existingDraftIndex >= 0 ? this.drafts[existingDraftIndex].id : Date.now().toString();
|
||
draft.timestamp = new Date().toISOString();
|
||
draft.isAutoSave = true;
|
||
|
||
if (existingDraftIndex >= 0) {
|
||
this.drafts[existingDraftIndex] = draft;
|
||
} else {
|
||
this.drafts.push(draft);
|
||
}
|
||
|
||
this.saveDraftsToStorage();
|
||
this.renderDrafts();
|
||
}
|
||
}
|
||
|
||
getDraftData() {
|
||
return {
|
||
to: this.toField.value,
|
||
cc: this.ccField.value,
|
||
bcc: this.bccField.value,
|
||
subject: this.subjectField.value,
|
||
priority: this.priorityField.value,
|
||
message: this.messageEditor.innerHTML,
|
||
attachments: this.attachments.map(file => ({
|
||
name: file.name,
|
||
size: file.size,
|
||
type: file.type
|
||
}))
|
||
};
|
||
}
|
||
|
||
loadDraft(draft) {
|
||
this.toField.value = draft.to || '';
|
||
this.ccField.value = draft.cc || '';
|
||
this.bccField.value = draft.bcc || '';
|
||
this.subjectField.value = draft.subject || '';
|
||
this.priorityField.value = draft.priority || 'normal';
|
||
this.messageEditor.innerHTML = draft.message || '';
|
||
|
||
// Note: File attachments cannot be restored for security reasons
|
||
this.attachments = [];
|
||
this.attachmentList.innerHTML = '';
|
||
|
||
if (draft.attachments && draft.attachments.length > 0) {
|
||
this.showMessage('Note: File attachments from drafts cannot be restored for security reasons.', 'warning');
|
||
}
|
||
|
||
this.updateCharCount();
|
||
this.showMessage('📝 Draft loaded successfully!', 'success');
|
||
}
|
||
|
||
async deleteDraft(draftId) {
|
||
const draftIndex = this.drafts.findIndex(draft => draft.id === draftId);
|
||
if (draftIndex >= 0) {
|
||
const draft = this.drafts[draftIndex];
|
||
|
||
// Delete from Firebase if it exists there
|
||
await this.deleteDraftFromFirebase(draft);
|
||
|
||
// Remove from local array
|
||
this.drafts.splice(draftIndex, 1);
|
||
this.saveDraftsToStorage();
|
||
this.renderDrafts();
|
||
this.showMessage('🗑️ Draft deleted.', 'success');
|
||
}
|
||
}
|
||
|
||
removeDraftIfExists() {
|
||
// Remove auto-saved draft when email is sent
|
||
this.drafts = this.drafts.filter(draft => !draft.isAutoSave);
|
||
this.saveDraftsToStorage();
|
||
this.renderDrafts();
|
||
}
|
||
|
||
saveDraftsToStorage() {
|
||
try {
|
||
localStorage.setItem('mailComposerDrafts', JSON.stringify(this.drafts));
|
||
} catch (error) {
|
||
console.warn('Failed to save drafts to localStorage:', error);
|
||
}
|
||
}
|
||
|
||
loadDrafts() {
|
||
try {
|
||
const savedDrafts = localStorage.getItem('mailComposerDrafts');
|
||
if (savedDrafts) {
|
||
this.drafts = JSON.parse(savedDrafts);
|
||
}
|
||
|
||
// Also load drafts from Firebase
|
||
this.loadDraftsFromFirebase();
|
||
|
||
this.renderDrafts();
|
||
} catch (error) {
|
||
console.warn('Failed to load drafts from localStorage:', error);
|
||
this.drafts = [];
|
||
}
|
||
}
|
||
|
||
renderDrafts() {
|
||
if (this.drafts.length === 0) {
|
||
this.draftsList.innerHTML = '<p class="no-drafts">No drafts saved yet</p>';
|
||
return;
|
||
}
|
||
|
||
const draftsHtml = this.drafts
|
||
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
|
||
.map(draft => {
|
||
const preview = this.getTextFromHtml(draft.message).substring(0, 100);
|
||
const date = new Date(draft.timestamp).toLocaleDateString();
|
||
const time = new Date(draft.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||
|
||
return `
|
||
<div class="draft-item" data-draft-id="${draft.id}">
|
||
<div class="draft-subject">${draft.subject || 'No Subject'} ${draft.isAutoSave ? '(Auto-saved)' : ''}</div>
|
||
<div class="draft-preview">${preview}${preview.length === 100 ? '...' : ''}</div>
|
||
<div class="draft-meta">
|
||
<span>To: ${draft.to || 'No recipient'}</span>
|
||
<span>${date} ${time}</span>
|
||
</div>
|
||
<div class="draft-actions">
|
||
<button type="button" class="draft-load" onclick="mailComposer.loadDraft(${JSON.stringify(draft).replace(/"/g, '"')})">Load</button>
|
||
<button type="button" class="draft-delete" onclick="mailComposer.deleteDraft('${draft.id}')">Delete</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
})
|
||
.join('');
|
||
|
||
this.draftsList.innerHTML = draftsHtml;
|
||
}
|
||
|
||
getTextFromHtml(html) {
|
||
const div = document.createElement('div');
|
||
div.innerHTML = html;
|
||
return div.textContent || div.innerText || '';
|
||
}
|
||
|
||
clearForm() {
|
||
this.form.reset();
|
||
this.messageEditor.innerHTML = '';
|
||
this.attachments = [];
|
||
this.attachmentList.innerHTML = '';
|
||
this.updateCharCount();
|
||
this.hideMessage();
|
||
}
|
||
|
||
showMessage(message, type) {
|
||
this.messageStatus.textContent = message;
|
||
this.messageStatus.className = `message-status ${type}`;
|
||
|
||
// Auto-hide success and warning messages after 5 seconds
|
||
if (type === 'success' || type === 'warning') {
|
||
setTimeout(() => this.hideMessage(), 5000);
|
||
}
|
||
}
|
||
|
||
hideMessage() {
|
||
this.messageStatus.className = 'message-status';
|
||
}
|
||
}
|
||
|
||
// Initialize the application when the DOM is loaded
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
window.mailComposer = new MailComposer();
|
||
});
|
||
|
||
// Prevent accidental page navigation when there's unsaved content
|
||
window.addEventListener('beforeunload', (e) => {
|
||
const composer = window.mailComposer;
|
||
if (composer) {
|
||
const draft = composer.getDraftData();
|
||
if (draft.to || draft.subject || draft.message) {
|
||
e.preventDefault();
|
||
e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
|
||
}
|
||
}
|
||
});
|