/**
* Message System JavaScript
* Handles interactive features for the modern message interface
*/
class MessageSystem {
constructor() {
this.init();
}
init() {
this.initSearch();
this.initFolderNavigation();
this.initMessageActions();
this.initComposeFeatures();
this.initKeyboardShortcuts();
this.initAutoSave();
this.initAttachments();
this.initTooltips();
}
/**
* Initialize search functionality
*/
initSearch() {
const searchInputs = document.querySelectorAll('.search-input');
searchInputs.forEach(input => {
let searchTimeout;
input.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
const searchTerm = e.target.value.toLowerCase();
searchTimeout = setTimeout(() => {
this.performSearch(searchTerm);
}, 300);
});
// Clear search on Escape key
input.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
e.target.value = '';
this.performSearch('');
}
});
});
}
/**
* Perform search across message items
*/
performSearch(searchTerm) {
const messageItems = document.querySelectorAll('.message-item');
let visibleCount = 0;
messageItems.forEach(item => {
const text = item.textContent.toLowerCase();
if (text.includes(searchTerm)) {
item.style.display = 'flex';
visibleCount++;
} else {
item.style.display = 'none';
}
});
// Show no results message if needed
this.updateSearchResults(visibleCount, searchTerm);
}
/**
* Update search results display
*/
updateSearchResults(count, searchTerm) {
let noResults = document.querySelector('.search-no-results');
if (count === 0 && searchTerm) {
if (!noResults) {
noResults = document.createElement('div');
noResults.className = 'search-no-results';
noResults.innerHTML = `
{% trans "No messages found" %}
{% trans "No messages match your search for" %} "${searchTerm}"
`;
const messagesList = document.querySelector('.messages-list');
if (messagesList) {
messagesList.appendChild(noResults);
}
}
} else if (noResults) {
noResults.remove();
}
}
/**
* Initialize folder navigation
*/
initFolderNavigation() {
const folderItems = document.querySelectorAll('.folder-item');
folderItems.forEach(item => {
item.addEventListener('click', (e) => {
// Remove active class from all items
folderItems.forEach(f => f.classList.remove('active'));
// Add active class to clicked item
item.classList.add('active');
// Add loading state
this.showLoadingState();
// Navigate to folder (if it's a link)
if (item.tagName === 'A') {
// Let the link handle navigation
return;
}
});
});
}
/**
* Initialize message actions
*/
initMessageActions() {
// Refresh button
const refreshBtn = document.querySelector('.action-btn[title="Refresh"]');
if (refreshBtn) {
refreshBtn.addEventListener('click', () => {
this.refreshMessages();
});
}
// Mark as read functionality
const markReadBtns = document.querySelectorAll('[onclick*="markAsRead"]');
markReadBtns.forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
this.markAsRead(btn);
});
});
// Delete message functionality
const deleteBtns = document.querySelectorAll('[onclick*="confirm"]');
deleteBtns.forEach(btn => {
btn.addEventListener('click', (e) => {
if (!this.confirmDelete()) {
e.preventDefault();
}
});
});
}
/**
* Initialize compose features
*/
initComposeFeatures() {
const form = document.getElementById('composeForm');
if (!form) return;
// Auto-resize textarea
const textarea = form.querySelector('textarea[name="content"]');
if (textarea) {
textarea.addEventListener('input', () => {
this.autoResizeTextarea(textarea);
});
}
// Rich text toolbar
const toolbarBtns = document.querySelectorAll('.toolbar-btn');
toolbarBtns.forEach(btn => {
btn.addEventListener('click', (e) => {
e.preventDefault();
this.handleToolbarAction(btn);
});
});
// Save draft button
const saveDraftBtn = document.getElementById('saveDraftBtn');
if (saveDraftBtn) {
saveDraftBtn.addEventListener('click', () => {
this.saveDraft();
});
}
// Form submission
form.addEventListener('submit', (e) => {
this.handleFormSubmit(e);
});
}
/**
* Initialize keyboard shortcuts
*/
initKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
// Ctrl/Cmd + K for search
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
const searchInput = document.querySelector('.search-input');
if (searchInput) {
searchInput.focus();
}
}
// Ctrl/Cmd + N for new message
if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
e.preventDefault();
const composeBtn = document.querySelector('.compose-btn');
if (composeBtn) {
window.location.href = composeBtn.href;
}
}
// Escape to close modals
if (e.key === 'Escape') {
this.closeModals();
}
});
}
/**
* Initialize auto-save functionality
*/
initAutoSave() {
const form = document.getElementById('composeForm');
if (!form) return;
let autoSaveTimer;
const inputs = form.querySelectorAll('input, textarea, select');
inputs.forEach(input => {
input.addEventListener('input', () => {
clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(() => {
this.autoSave();
}, 30000); // Auto-save after 30 seconds
});
});
}
/**
* Initialize attachment handling
*/
initAttachments() {
const attachBtn = document.querySelector('.toolbar-btn[title*="Attach"]');
if (attachBtn) {
attachBtn.addEventListener('click', () => {
this.showAttachmentDialog();
});
}
}
/**
* Initialize tooltips
*/
initTooltips() {
const tooltipElements = document.querySelectorAll('[title]');
tooltipElements.forEach(element => {
element.addEventListener('mouseenter', (e) => {
this.showTooltip(e.target);
});
element.addEventListener('mouseleave', (e) => {
this.hideTooltip(e.target);
});
});
}
/**
* Refresh messages with animation
*/
refreshMessages() {
const refreshBtn = document.querySelector('.action-btn[title="Refresh"]');
if (refreshBtn) {
const icon = refreshBtn.querySelector('i');
icon.classList.add('fa-spin');
setTimeout(() => {
icon.classList.remove('fa-spin');
location.reload();
}, 1000);
}
}
/**
* Mark message as read
*/
async markAsRead(button) {
const messageId = button.getAttribute('data-message-id');
if (!messageId) return;
try {
const response = await fetch(`/messages/${messageId}/mark-read/`, {
method: 'POST',
headers: {
'X-CSRFToken': this.getCSRFToken(),
'Content-Type': 'application/json',
},
});
const data = await response.json();
if (data.success) {
this.showNotification('{% trans "Message marked as read" %}', 'success');
location.reload();
} else {
this.showNotification('{% trans "Failed to mark message as read" %}', 'error');
}
} catch (error) {
console.error('Error marking message as read:', error);
this.showNotification('{% trans "An error occurred" %}', 'error');
}
}
/**
* Confirm delete action
*/
confirmDelete() {
return confirm('{% trans "Are you sure you want to delete this message?" %}');
}
/**
* Auto-resize textarea
*/
autoResizeTextarea(textarea) {
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
}
/**
* Handle toolbar actions
*/
handleToolbarAction(button) {
const action = button.getAttribute('title');
const textarea = document.querySelector('textarea[name="content"]');
if (!textarea) return;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
switch (action) {
case '{% trans "Bold" %}':
this.wrapText(textarea, '**', '**');
break;
case '{% trans "Italic" %}':
this.wrapText(textarea, '*', '*');
break;
case '{% trans "Underline" %}':
this.wrapText(textarea, '__', '__');
break;
case '{% trans "Bullet List" %}':
this.insertList(textarea, '- ');
break;
case '{% trans "Numbered List" %}':
this.insertList(textarea, '1. ');
break;
case '{% trans "Insert Link" %}':
this.insertLink(textarea);
break;
case '{% trans "Insert Image" %}':
this.insertImage(textarea);
break;
case '{% trans "Attach File" %}':
this.showAttachmentDialog();
break;
}
}
/**
* Wrap selected text with formatting
*/
wrapText(textarea, before, after) {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
const replacement = before + selectedText + after;
textarea.value = textarea.value.substring(0, start) + replacement + textarea.value.substring(end);
textarea.selectionStart = start + before.length;
textarea.selectionEnd = start + before.length + selectedText.length;
textarea.focus();
}
/**
* Insert list
*/
insertList(textarea, marker) {
const start = textarea.selectionStart;
const text = marker + '\n';
textarea.value = textarea.value.substring(0, start) + text + textarea.value.substring(start);
textarea.selectionStart = textarea.selectionEnd = start + text.length;
textarea.focus();
}
/**
* Insert link
*/
insertLink(textarea) {
const url = prompt('{% trans "Enter URL:" %}', 'https://');
if (url) {
const link = `[${url}](${url})`;
this.insertAtCursor(textarea, link);
}
}
/**
* Insert image
*/
insertImage(textarea) {
const url = prompt('{% trans "Enter image URL:" %}', 'https://');
if (url) {
const image = ``;
this.insertAtCursor(textarea, image);
}
}
/**
* Insert text at cursor position
*/
insertAtCursor(textarea, text) {
const start = textarea.selectionStart;
textarea.value = textarea.value.substring(0, start) + text + textarea.value.substring(start);
textarea.selectionStart = textarea.selectionEnd = start + text.length;
textarea.focus();
}
/**
* Save draft
*/
async saveDraft() {
const form = document.getElementById('composeForm');
if (!form) return;
const formData = new FormData(form);
try {
const response = await fetch('/messages/save-draft/', {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': this.getCSRFToken(),
},
});
const data = await response.json();
if (data.success) {
this.showNotification('{% trans "Draft saved" %}', 'success');
} else {
this.showNotification('{% trans "Failed to save draft" %}', 'error');
}
} catch (error) {
console.error('Error saving draft:', error);
this.showNotification('{% trans "An error occurred" %}', 'error');
}
}
/**
* Auto-save draft
*/
async autoSave() {
const form = document.getElementById('composeForm');
if (!form) return;
// Only auto-save if form has content
const hasContent = form.querySelector('textarea[name="content"]').value.trim() ||
form.querySelector('input[name="subject"]').value.trim();
if (!hasContent) return;
try {
const formData = new FormData(form);
await fetch('/messages/auto-save-draft/', {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': this.getCSRFToken(),
},
});
console.log('Draft auto-saved');
} catch (error) {
console.error('Error auto-saving draft:', error);
}
}
/**
* Handle form submission
*/
handleFormSubmit(e) {
const form = e.target;
const submitBtn = form.querySelector('button[type="submit"]');
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.innerHTML = '