botui/ui/suite/mail/mail.html
Rodrigo Rodriguez (Pragmatismo) d8e52bf330 feat(auth): Add user profile loading and auth state management
- Add JavaScript to load user profile from /api/auth/me endpoint
- Save access_token to localStorage/sessionStorage on login
- Update user menu to show actual user name and email
- Toggle Sign in/Sign out based on authentication state
- Add IDs to user menu elements for dynamic updates
2026-01-06 22:57:00 -03:00

2467 lines
75 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div class="mail-layout">
<!-- Sidebar -->
<div class="panel mail-sidebar">
<div class="compose-section">
<button class="compose-btn" onclick="openCompose()">
<svg
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M12 5v14M5 12h14" />
</svg>
<span data-i18n="email-compose">Compose</span>
</button>
</div>
<!-- Folder List -->
<div class="folders-section">
<div
class="nav-item active"
data-folder="inbox"
hx-get="/api/ui/email/list?folder=inbox"
hx-target="#mail-list"
hx-swap="innerHTML"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="22 12 16 12 14 15 10 15 8 12 2 12" />
<path
d="M5.45 5.11L2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z"
/>
</svg>
<span data-i18n="email-inbox">Inbox</span>
<span class="folder-badge unread" id="inbox-count">0</span>
</div>
<div
class="nav-item"
data-folder="starred"
hx-get="/api/ui/email/list?folder=starred"
hx-target="#mail-list"
hx-swap="innerHTML"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polygon
points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"
/>
</svg>
<span data-i18n="email-starred">Starred</span>
</div>
<div
class="nav-item"
data-folder="sent"
hx-get="/api/ui/email/list?folder=sent"
hx-target="#mail-list"
hx-swap="innerHTML"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="22" y1="2" x2="11" y2="13" />
<polygon points="22 2 15 22 11 13 2 9 22 2" />
</svg>
<span data-i18n="email-sent">Sent</span>
</div>
<div
class="nav-item"
data-folder="scheduled"
hx-get="/api/ui/email/list?folder=scheduled"
hx-target="#mail-list"
hx-swap="innerHTML"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="10" />
<polyline points="12 6 12 12 16 14" />
</svg>
<span data-i18n="email-scheduled">Scheduled</span>
<span class="folder-badge" id="scheduled-count">0</span>
</div>
<div
class="nav-item"
data-folder="drafts"
hx-get="/api/ui/email/list?folder=drafts"
hx-target="#mail-list"
hx-swap="innerHTML"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"
/>
<polyline points="14 2 14 8 20 8" />
<line x1="16" y1="13" x2="8" y2="13" />
<line x1="16" y1="17" x2="8" y2="17" />
</svg>
<span data-i18n="email-drafts">Drafts</span>
<span class="folder-badge" id="drafts-count">0</span>
</div>
<div
class="nav-item"
data-folder="tracking"
hx-get="/api/email/tracking/list"
hx-target="#mail-list"
hx-swap="innerHTML"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
<circle cx="12" cy="12" r="3" />
</svg>
<span data-i18n="email-tracking">Tracking</span>
</div>
<div
class="nav-item"
data-folder="spam"
hx-get="/api/ui/email/list?folder=spam"
hx-target="#mail-list"
hx-swap="innerHTML"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
/>
<line x1="12" y1="9" x2="12" y2="13" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
<span data-i18n="email-spam">Spam</span>
</div>
<div
class="nav-item"
data-folder="trash"
hx-get="/api/ui/email/list?folder=trash"
hx-target="#mail-list"
hx-swap="innerHTML"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="3 6 5 6 21 6" />
<path
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
/>
</svg>
<span data-i18n="email-trash">Trash</span>
</div>
</div>
<!-- Accounts Section -->
<div class="labels-section">
<div class="section-header">
<span>Accounts</span>
<button
class="icon-btn"
onclick="document.getElementById('add-account-modal').showModal()"
title="Add account"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
</button>
</div>
<div
id="accounts-list"
hx-get="/api/ui/email/accounts"
hx-trigger="load"
hx-swap="innerHTML"
>
<!-- Accounts loaded here -->
</div>
</div>
<!-- Labels Section -->
<div class="labels-section">
<div class="section-header">
<span>Labels</span>
<button
class="icon-btn"
onclick="openLabelManager()"
title="Manage labels"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="12" y1="5" x2="12" y2="19" />
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
</button>
</div>
<div
id="labels-list"
hx-get="/api/ui/email/labels"
hx-trigger="load"
hx-swap="innerHTML"
>
<div class="label-item" style="--label-color: #ef4444">
<span class="label-dot"></span>
<span>Important</span>
</div>
<div class="label-item" style="--label-color: #f59e0b">
<span class="label-dot"></span>
<span>Work</span>
</div>
<div class="label-item" style="--label-color: #22c55e">
<span class="label-dot"></span>
<span>Personal</span>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="quick-actions">
<button
class="quick-action-btn"
onclick="openTemplates()"
title="Email Templates"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
<line x1="3" y1="9" x2="21" y2="9" />
<line x1="9" y1="21" x2="9" y2="9" />
</svg>
<span>Templates</span>
</button>
<button
class="quick-action-btn"
onclick="openSignatures()"
title="Signatures"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"
/>
</svg>
<span>Signatures</span>
</button>
<button
class="quick-action-btn"
onclick="openRules()"
title="Email Rules"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polygon
points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"
/>
</svg>
<span>Rules</span>
</button>
<button
class="quick-action-btn"
onclick="openAutoResponder()"
title="Out of Office"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="10" />
<path d="M8 14s1.5 2 4 2 4-2 4-2" />
<line x1="9" y1="9" x2="9.01" y2="9" />
<line x1="15" y1="9" x2="15.01" y2="9" />
</svg>
<span>Auto-reply</span>
</button>
</div>
</div>
<!-- Mail List -->
<div class="panel mail-list">
<div class="list-header">
<div class="list-header-left">
<input
type="checkbox"
class="select-all"
onchange="toggleSelectAll(this)"
title="Select all"
/>
<h3 id="folder-title">Inbox</h3>
</div>
<div class="list-header-right">
<div class="search-box">
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
<input
type="text"
placeholder="Search emails..."
id="email-search"
hx-get="/api/ui/email/search"
hx-trigger="keyup changed delay:300ms"
hx-target="#mail-list"
hx-include="this"
name="q"
/>
</div>
<button
class="icon-btn"
onclick="refreshMailList()"
title="Refresh"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="23 4 23 10 17 10" />
<polyline points="1 20 1 14 7 14" />
<path
d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"
/>
</svg>
</button>
</div>
</div>
<!-- Bulk Actions Bar (hidden by default) -->
<div class="bulk-actions" id="bulk-actions" style="display: none">
<span class="selected-count">0 selected</span>
<button
class="bulk-btn"
onclick="archiveSelected()"
title="Archive"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="21 8 21 21 3 21 3 8" />
<rect x="1" y="3" width="22" height="5" />
<line x1="10" y1="12" x2="14" y2="12" />
</svg>
</button>
<button
class="bulk-btn"
onclick="markAsRead()"
title="Mark as read"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
</button>
<button
class="bulk-btn"
onclick="addLabelToSelected()"
title="Add label"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"
/>
<line x1="7" y1="7" x2="7.01" y2="7" />
</svg>
</button>
<button
class="bulk-btn danger"
onclick="deleteSelected()"
title="Delete"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="3 6 5 6 21 6" />
<path
d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
/>
</svg>
</button>
</div>
<div
id="mail-list"
hx-get="/api/ui/email/list?folder=inbox"
hx-trigger="load"
hx-swap="innerHTML"
>
<div class="loading-state">
<div class="spinner"></div>
<p>Loading emails...</p>
</div>
</div>
</div>
<!-- Mail Content -->
<div class="panel mail-content">
<div id="mail-content">
<div class="empty-state">
<svg
width="64"
height="64"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
>
<path
d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"
/>
<polyline points="22,6 12,13 2,6" />
</svg>
<h3>Select an email to read</h3>
<p>Choose an email from the list to view its contents</p>
</div>
</div>
</div>
</div>
<!-- Compose Modal -->
<dialog id="compose-modal" class="modal">
<div class="modal-content modal-large">
<div class="modal-header">
<h2>New Message</h2>
<div class="compose-actions-top">
<button
class="icon-btn"
onclick="minimizeCompose()"
title="Minimize"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="5" y1="12" x2="19" y2="12" />
</svg>
</button>
<button class="icon-btn" onclick="closeCompose()" title="Close">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
</div>
<form
id="compose-form"
hx-post="/api/email/send"
hx-target="#mail-content"
hx-swap="innerHTML"
>
<div class="compose-fields">
<div class="field-row">
<label>To:</label>
<input
type="text"
name="to"
id="compose-to"
placeholder="Recipients"
required
autocomplete="email"
/>
<button
type="button"
class="field-toggle"
onclick="toggleCcBcc()"
>
Cc/Bcc
</button>
</div>
<div class="field-row cc-bcc" style="display: none">
<label>Cc:</label>
<input
type="text"
name="cc"
id="compose-cc"
placeholder="Carbon copy"
/>
</div>
<div class="field-row cc-bcc" style="display: none">
<label>Bcc:</label>
<input
type="text"
name="bcc"
id="compose-bcc"
placeholder="Blind carbon copy"
/>
</div>
<div class="field-row">
<label>Subject:</label>
<input
type="text"
name="subject"
id="compose-subject"
placeholder="Subject"
/>
</div>
</div>
<div class="compose-toolbar">
<button
type="button"
class="toolbar-btn"
title="Bold"
onclick="formatText('bold')"
>
<b>B</b>
</button>
<button
type="button"
class="toolbar-btn"
title="Italic"
onclick="formatText('italic')"
>
<i>I</i>
</button>
<button
type="button"
class="toolbar-btn"
title="Underline"
onclick="formatText('underline')"
>
<u>U</u>
</button>
<span class="toolbar-divider"></span>
<button
type="button"
class="toolbar-btn"
title="Bullet list"
onclick="formatText('insertUnorderedList')"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="8" y1="6" x2="21" y2="6" />
<line x1="8" y1="12" x2="21" y2="12" />
<line x1="8" y1="18" x2="21" y2="18" />
<line x1="3" y1="6" x2="3.01" y2="6" />
<line x1="3" y1="12" x2="3.01" y2="12" />
<line x1="3" y1="18" x2="3.01" y2="18" />
</svg>
</button>
<button
type="button"
class="toolbar-btn"
title="Numbered list"
onclick="formatText('insertOrderedList')"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="10" y1="6" x2="21" y2="6" />
<line x1="10" y1="12" x2="21" y2="12" />
<line x1="10" y1="18" x2="21" y2="18" />
<path d="M4 6h1v4" />
<path d="M4 10h2" />
<path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1" />
</svg>
</button>
<span class="toolbar-divider"></span>
<button
type="button"
class="toolbar-btn"
title="Attach file"
onclick="attachFile()"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"
/>
</svg>
</button>
<button
type="button"
class="toolbar-btn"
title="Insert link"
onclick="insertLink()"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"
/>
<path
d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"
/>
</svg>
</button>
<button
type="button"
class="toolbar-btn"
title="Insert image"
onclick="insertImage()"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
ry="2"
/>
<circle cx="8.5" cy="8.5" r="1.5" />
<polyline points="21 15 16 10 5 21" />
</svg>
</button>
<span class="toolbar-divider"></span>
<button
type="button"
class="toolbar-btn"
title="Insert template"
onclick="showTemplateSelector()"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="3"
width="18"
height="18"
rx="2"
ry="2"
/>
<line x1="3" y1="9" x2="21" y2="9" />
<line x1="9" y1="21" x2="9" y2="9" />
</svg>
</button>
<button
type="button"
class="toolbar-btn"
title="Insert signature"
onclick="insertSignature()"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path
d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"
/>
</svg>
</button>
</div>
<div
class="compose-body"
contenteditable="true"
id="compose-body"
placeholder="Write your message..."
></div>
<input type="hidden" name="body" id="compose-body-hidden" />
<div class="compose-attachments" id="compose-attachments"></div>
<div class="compose-footer">
<div class="compose-options">
<label class="option-checkbox">
<input type="checkbox" name="track_opens" checked />
<span>Track opens</span>
</label>
<label class="option-checkbox">
<input type="checkbox" name="request_receipt" />
<span>Request read receipt</span>
</label>
<select name="priority" class="priority-select">
<option value="normal">Normal priority</option>
<option value="high">High priority</option>
<option value="low">Low priority</option>
</select>
</div>
<div class="compose-actions">
<button
type="button"
class="btn-secondary"
hx-post="/api/email/draft"
hx-include="#compose-form"
hx-swap="none"
onclick="prepareSubmit()"
>
Save Draft
</button>
<div class="send-group">
<button
type="submit"
class="btn-primary"
onclick="prepareSubmit()"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="22" y1="2" x2="11" y2="13" />
<polygon points="22 2 15 22 11 13 2 9 22 2" />
</svg>
Send
</button>
<button
type="button"
class="btn-primary dropdown-toggle"
onclick="toggleScheduleMenu()"
>
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<polyline points="6 9 12 15 18 9" />
</svg>
</button>
<div class="schedule-menu" id="schedule-menu">
<div
class="schedule-option"
onclick="scheduleSend('tomorrow-morning')"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="5" />
<line x1="12" y1="1" x2="12" y2="3" />
<line x1="12" y1="21" x2="12" y2="23" />
<line
x1="4.22"
y1="4.22"
x2="5.64"
y2="5.64"
/>
<line
x1="18.36"
y1="18.36"
x2="19.78"
y2="19.78"
/>
<line x1="1" y1="12" x2="3" y2="12" />
<line x1="21" y1="12" x2="23" y2="12" />
<line
x1="4.22"
y1="19.78"
x2="5.64"
y2="18.36"
/>
<line
x1="18.36"
y1="5.64"
x2="19.78"
y2="4.22"
/>
</svg>
<span>Tomorrow morning (8:00 AM)</span>
</div>
<div
class="schedule-option"
onclick="scheduleSend('tomorrow-afternoon')"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="10" />
<polyline points="12 6 12 12 16 14" />
</svg>
<span>Tomorrow afternoon (1:00 PM)</span>
</div>
<div
class="schedule-option"
onclick="scheduleSend('monday')"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="3"
y="4"
width="18"
height="18"
rx="2"
ry="2"
/>
<line x1="16" y1="2" x2="16" y2="6" />
<line x1="8" y1="2" x2="8" y2="6" />
<line x1="3" y1="10" x2="21" y2="10" />
</svg>
<span>Monday morning (8:00 AM)</span>
</div>
<div class="schedule-divider"></div>
<div
class="schedule-option"
onclick="openCustomSchedule()"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="12" cy="12" r="3" />
<path
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"
/>
</svg>
<span>Pick date & time...</span>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</dialog>
<!-- Templates Modal -->
<dialog id="templates-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Email Templates</h2>
<button class="icon-btn" onclick="closeTemplates()">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
<div
class="templates-list"
id="templates-list"
hx-get="/api/ui/email/templates"
hx-trigger="load"
hx-swap="innerHTML"
>
<div class="loading-state"><div class="spinner"></div></div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeTemplates()">
Cancel
</button>
<button class="btn-primary" onclick="createNewTemplate()">
+ New Template
</button>
</div>
</div>
</dialog>
<!-- Signatures Modal -->
<dialog id="signatures-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Email Signatures</h2>
<button class="icon-btn" onclick="closeSignatures()">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
<div
class="signatures-list"
id="signatures-list"
hx-get="/api/ui/email/signatures"
hx-trigger="load"
hx-swap="innerHTML"
>
<div class="loading-state"><div class="spinner"></div></div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeSignatures()">
Cancel
</button>
<button class="btn-primary" onclick="createNewSignature()">
+ New Signature
</button>
</div>
</div>
</dialog>
<!-- Rules Modal -->
<dialog id="rules-modal" class="modal">
<div class="modal-content modal-large">
<div class="modal-header">
<h2>Email Rules</h2>
<button class="icon-btn" onclick="closeRules()">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
<div
class="rules-list"
id="rules-list"
hx-get="/api/ui/email/rules"
hx-trigger="load"
hx-swap="innerHTML"
>
<div class="loading-state"><div class="spinner"></div></div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeRules()">Cancel</button>
<button class="btn-primary" onclick="createNewRule()">
+ New Rule
</button>
</div>
</div>
</dialog>
<!-- Auto-Responder Modal -->
<dialog id="autoresponder-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Automatic Replies</h2>
<button class="icon-btn" onclick="closeAutoResponder()">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
<form
id="autoresponder-form"
hx-post="/api/ui/email/auto-responder"
hx-swap="none"
>
<div class="form-group">
<label class="toggle-label">
<input
type="checkbox"
name="enabled"
id="autoresponder-enabled"
/>
<span class="toggle-switch"></span>
<span>Send automatic replies</span>
</label>
</div>
<div class="form-group">
<label>Start date</label>
<input
type="datetime-local"
name="start_date"
id="autoresponder-start"
/>
</div>
<div class="form-group">
<label>End date</label>
<input
type="datetime-local"
name="end_date"
id="autoresponder-end"
/>
</div>
<div class="form-group">
<label>Subject</label>
<input
type="text"
name="subject"
value="Out of Office"
placeholder="Reply subject"
/>
</div>
<div class="form-group">
<label>Message</label>
<textarea
name="body"
rows="6"
placeholder="Your automatic reply message..."
></textarea>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" name="internal_only" />
<span>Send only to people in my organization</span>
</label>
</div>
</form>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeAutoResponder()">
Cancel
</button>
<button class="btn-primary" onclick="saveAutoResponder()">
Save
</button>
</div>
</div>
</dialog>
<!-- Schedule Picker Modal -->
<!-- Add Account Modal -->
<dialog id="add-account-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2>Add Email Account</h2>
<button
class="icon-btn"
onclick="document.getElementById('add-account-modal').close()"
>
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
<form
hx-post="/api/email/accounts/add"
hx-target="#accounts-list"
hx-swap="innerHTML"
hx-on::after-request="if(event.detail.successful) document.getElementById('add-account-modal').close()"
>
<div class="form-group">
<label>Account Name</label>
<input
type="text"
name="name"
placeholder="Work, Personal, etc."
required
/>
</div>
<div class="form-group">
<label>Email Address</label>
<input
type="email"
name="email"
placeholder="you@example.com"
required
/>
</div>
<div class="form-group">
<label>IMAP Server</label>
<input
type="text"
name="imap_server"
placeholder="imap.example.com"
required
/>
</div>
<div class="form-group">
<label>IMAP Port</label>
<input type="number" name="imap_port" value="993" required />
</div>
<div class="form-group">
<label>SMTP Server</label>
<input
type="text"
name="smtp_server"
placeholder="smtp.example.com"
required
/>
</div>
<div class="form-group">
<label>SMTP Port</label>
<input type="number" name="smtp_port" value="587" required />
</div>
<div class="form-group">
<label>Username</label>
<input
type="text"
name="username"
placeholder="username or email"
required
/>
</div>
<div class="form-group">
<label>Password</label>
<input
type="password"
name="password"
placeholder="••••••••"
required
autocomplete="current-password"
/>
</div>
<div class="form-group">
<label class="checkbox-label">
<input type="checkbox" name="use_ssl" checked />
<span>Use SSL/TLS</span>
</label>
</div>
<div class="modal-footer">
<button
type="button"
class="btn-secondary"
onclick="document.getElementById('add-account-modal').close()"
>
Cancel
</button>
<button type="submit" class="btn-primary">Add Account</button>
</div>
</form>
</div>
</dialog>
<dialog id="schedule-modal" class="modal">
<div class="modal-content modal-small">
<div class="modal-header">
<h2>Schedule Send</h2>
<button class="icon-btn" onclick="closeScheduleModal()">
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
<div class="schedule-form">
<div class="form-group">
<label>Date</label>
<input type="date" id="schedule-date" min="" />
</div>
<div class="form-group">
<label>Time</label>
<input type="time" id="schedule-time" value="09:00" />
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" onclick="closeScheduleModal()">
Cancel
</button>
<button class="btn-primary" onclick="confirmSchedule()">
Schedule
</button>
</div>
</div>
</dialog>
<style>
.mail-layout {
display: grid;
grid-template-columns: 260px 380px 1fr;
height: calc(100vh - 64px);
background: var(--bg);
}
.panel {
background: var(--surface);
overflow-y: auto;
border-right: 1px solid var(--border);
}
.panel:last-child {
border-right: none;
}
/* Sidebar */
.mail-sidebar {
display: flex;
flex-direction: column;
}
.compose-section {
padding: 1rem;
border-bottom: 1px solid var(--border);
}
.compose-btn {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
background: var(--primary);
color: white;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.compose-btn:hover {
background: var(--primary-hover);
}
.folders-section {
padding: 0.5rem 0;
flex: 1;
}
.nav-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.625rem 1rem;
color: var(--text);
cursor: pointer;
transition: background 0.2s;
font-size: 0.875rem;
}
.nav-item:hover {
background: var(--surface-hover);
}
.nav-item.active {
background: var(--primary-light);
color: var(--primary);
}
.nav-item svg {
flex-shrink: 0;
}
.folder-badge {
margin-left: auto;
background: var(--surface-hover);
color: var(--text-secondary);
padding: 0.125rem 0.5rem;
border-radius: 10px;
font-size: 0.75rem;
font-weight: 500;
}
.folder-badge.unread {
background: var(--primary);
color: white;
}
.labels-section {
padding: 0.75rem 0;
border-top: 1px solid var(--border);
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1rem;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-secondary);
}
.icon-btn {
padding: 0.375rem;
background: transparent;
border: none;
color: var(--text-secondary);
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.icon-btn:hover {
background: var(--surface-hover);
color: var(--text);
}
.label-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 1rem;
font-size: 0.875rem;
cursor: pointer;
transition: background 0.2s;
}
.label-item:hover {
background: var(--surface-hover);
}
.label-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--label-color);
}
.quick-actions {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
padding: 0.75rem;
border-top: 1px solid var(--border);
}
.quick-action-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.25rem;
padding: 0.5rem;
background: transparent;
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text-secondary);
font-size: 0.7rem;
cursor: pointer;
transition: all 0.2s;
}
.quick-action-btn:hover {
background: var(--surface-hover);
color: var(--text);
border-color: var(--text-secondary);
}
/* Mail List */
.list-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--border);
gap: 0.75rem;
}
.list-header-left {
display: flex;
align-items: center;
gap: 0.75rem;
}
.list-header-left h3 {
font-size: 1rem;
font-weight: 600;
}
.list-header-right {
display: flex;
align-items: center;
gap: 0.5rem;
}
.search-box {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.375rem 0.75rem;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
}
.search-box input {
background: transparent;
border: none;
outline: none;
color: var(--text);
font-size: 0.875rem;
width: 140px;
}
.search-box input::placeholder {
color: var(--text-secondary);
}
.bulk-actions {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--primary-light);
border-bottom: 1px solid var(--border);
}
.selected-count {
font-size: 0.875rem;
color: var(--primary);
font-weight: 500;
margin-right: auto;
}
.bulk-btn {
padding: 0.375rem 0.5rem;
background: transparent;
border: none;
color: var(--text-secondary);
border-radius: 4px;
cursor: pointer;
}
.bulk-btn:hover {
background: var(--surface-hover);
color: var(--text);
}
.bulk-btn.danger:hover {
color: var(--error);
}
.mail-item {
display: flex;
flex-direction: column;
padding: 0.875rem 1rem;
border-bottom: 1px solid var(--border);
cursor: pointer;
transition: background 0.2s;
}
.mail-item:hover {
background: var(--surface-hover);
}
.mail-item.unread {
background: var(--primary-light);
}
.mail-item.unread .mail-from {
font-weight: 600;
}
.mail-item.selected {
background: var(--primary-light);
border-left: 3px solid var(--primary);
}
.mail-item-header {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.25rem;
}
.mail-item-header input[type="checkbox"] {
margin-right: 0.25rem;
}
.mail-from {
flex: 1;
font-size: 0.875rem;
color: var(--text);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mail-time {
font-size: 0.75rem;
color: var(--text-secondary);
}
.mail-subject {
font-size: 0.875rem;
color: var(--text);
margin-bottom: 0.25rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mail-preview {
font-size: 0.8rem;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mail-labels {
display: flex;
gap: 0.25rem;
margin-top: 0.375rem;
}
.mail-label {
padding: 0.125rem 0.375rem;
border-radius: 4px;
font-size: 0.65rem;
font-weight: 500;
}
/* Mail Content */
.mail-content {
display: flex;
flex-direction: column;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--text-secondary);
text-align: center;
padding: 2rem;
}
.empty-state svg {
margin-bottom: 1rem;
opacity: 0.5;
}
.empty-state h3 {
font-size: 1.125rem;
margin-bottom: 0.5rem;
color: var(--text);
}
.empty-state p {
font-size: 0.875rem;
}
.loading-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
color: var(--text-secondary);
}
.spinner {
width: 24px;
height: 24px;
border: 2px solid var(--border);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-bottom: 0.5rem;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* Modals - dialog elements are hidden by default */
dialog.modal {
position: fixed;
inset: 0;
background: transparent;
border: none;
padding: 1rem;
z-index: 1000;
max-width: 100vw;
max-height: 100vh;
width: 100%;
height: 100%;
display: none;
}
dialog.modal[open] {
display: flex;
align-items: center;
justify-content: center;
}
dialog.modal::backdrop {
background: rgba(0, 0, 0, 0.5);
}
.modal-content {
background: var(--surface);
border-radius: 12px;
width: 100%;
max-width: 500px;
max-height: 80vh;
display: flex;
flex-direction: column;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
}
.modal-large {
max-width: 700px;
}
.modal-small {
max-width: 320px;
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--border);
}
.modal-header h2 {
font-size: 1.125rem;
font-weight: 600;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
padding: 1rem 1.25rem;
border-top: 1px solid var(--border);
}
/* Compose */
.compose-fields {
padding: 0.75rem 1.25rem;
border-bottom: 1px solid var(--border);
}
.field-row {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 0;
border-bottom: 1px solid var(--border);
}
.field-row:last-child {
border-bottom: none;
}
.field-row label {
font-size: 0.875rem;
color: var(--text-secondary);
width: 60px;
flex-shrink: 0;
}
.field-row input {
flex: 1;
background: transparent;
border: none;
outline: none;
color: var(--text);
font-size: 0.875rem;
}
.field-toggle {
padding: 0.25rem 0.5rem;
background: transparent;
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-secondary);
font-size: 0.75rem;
cursor: pointer;
}
.field-toggle:hover {
background: var(--surface-hover);
}
.compose-toolbar {
display: flex;
align-items: center;
gap: 0.25rem;
padding: 0.5rem 1.25rem;
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
}
.toolbar-btn {
padding: 0.375rem 0.5rem;
background: transparent;
border: none;
color: var(--text-secondary);
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
min-width: 28px;
}
.toolbar-btn:hover {
background: var(--surface-hover);
color: var(--text);
}
.toolbar-divider {
width: 1px;
height: 20px;
background: var(--border);
margin: 0 0.25rem;
}
.compose-body {
flex: 1;
min-height: 200px;
padding: 1rem 1.25rem;
font-size: 0.875rem;
line-height: 1.6;
color: var(--text);
outline: none;
overflow-y: auto;
}
.compose-body:empty::before {
content: attr(placeholder);
color: var(--text-secondary);
}
.compose-attachments {
padding: 0 1.25rem;
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.compose-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1.25rem;
border-top: 1px solid var(--border);
flex-wrap: wrap;
gap: 0.75rem;
}
.compose-options {
display: flex;
align-items: center;
gap: 1rem;
flex-wrap: wrap;
}
.option-checkbox {
display: flex;
align-items: center;
gap: 0.375rem;
font-size: 0.8rem;
color: var(--text-secondary);
cursor: pointer;
}
.priority-select {
padding: 0.25rem 0.5rem;
background: transparent;
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-secondary);
font-size: 0.8rem;
}
.compose-actions {
display: flex;
align-items: center;
gap: 0.5rem;
}
.send-group {
display: flex;
position: relative;
}
.send-group .btn-primary:first-child {
border-radius: 6px 0 0 6px;
}
.send-group .dropdown-toggle {
padding: 0.625rem 0.5rem;
border-radius: 0 6px 6px 0;
border-left: 1px solid rgba(255, 255, 255, 0.2);
}
.schedule-menu {
position: absolute;
bottom: 100%;
right: 0;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 0.5rem 0;
min-width: 240px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
display: none;
z-index: 10;
margin-bottom: 0.5rem;
}
.schedule-menu.show {
display: block;
}
.schedule-option {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.625rem 1rem;
cursor: pointer;
transition: background 0.2s;
font-size: 0.875rem;
}
.schedule-option:hover {
background: var(--surface-hover);
}
.schedule-divider {
height: 1px;
background: var(--border);
margin: 0.5rem 0;
}
.btn-primary {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.625rem 1rem;
background: var(--primary);
color: white;
border: none;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
.btn-primary:hover {
background: var(--primary-hover);
}
.btn-secondary {
padding: 0.625rem 1rem;
background: transparent;
border: 1px solid var(--border);
color: var(--text);
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.btn-secondary:hover {
background: var(--surface-hover);
}
.form-group {
margin-bottom: 1rem;
padding: 0 1.25rem;
}
.form-group:first-child {
padding-top: 1rem;
}
.form-group label {
display: block;
font-size: 0.875rem;
font-weight: 500;
margin-bottom: 0.375rem;
color: var(--text);
}
.form-group input[type="text"],
.form-group input[type="date"],
.form-group input[type="time"],
.form-group input[type="datetime-local"],
.form-group textarea {
width: 100%;
padding: 0.625rem 0.75rem;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--text);
font-size: 0.875rem;
}
.form-group textarea {
resize: vertical;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
cursor: pointer;
}
.toggle-label {
display: flex;
align-items: center;
gap: 0.75rem;
cursor: pointer;
}
.toggle-switch {
position: relative;
width: 44px;
height: 24px;
background: var(--border);
border-radius: 12px;
transition: background 0.2s;
}
.toggle-switch::after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
transition: transform 0.2s;
}
input:checked + .toggle-switch {
background: var(--primary);
}
input:checked + .toggle-switch::after {
transform: translateX(20px);
}
.compose-actions-top {
display: flex;
gap: 0.25rem;
}
/* Responsive */
@media (max-width: 1024px) {
.mail-layout {
grid-template-columns: 220px 1fr;
}
.mail-content {
display: none;
}
.mail-content.active {
display: flex;
position: fixed;
inset: 64px 0 0 0;
z-index: 100;
}
}
@media (max-width: 768px) {
.mail-layout {
grid-template-columns: 1fr;
}
.mail-sidebar {
display: none;
}
.mail-sidebar.active {
display: flex;
position: fixed;
inset: 64px 0 0 0;
z-index: 100;
width: 280px;
}
.modal-content {
max-height: 90vh;
}
.compose-toolbar {
display: none;
}
}
</style>
<script>
let selectedEmails = new Set();
let currentFolder = "inbox";
function openCompose() {
document.getElementById("compose-modal").showModal();
}
function closeCompose() {
document.getElementById("compose-modal").close();
}
function minimizeCompose() {
closeCompose();
}
function toggleCcBcc() {
document.querySelectorAll(".cc-bcc").forEach((el) => {
el.style.display = el.style.display === "none" ? "flex" : "none";
});
}
function toggleScheduleMenu() {
document.getElementById("schedule-menu").classList.toggle("show");
}
function scheduleSend(option) {
let date = new Date();
switch (option) {
case "tomorrow-morning":
date.setDate(date.getDate() + 1);
date.setHours(8, 0, 0, 0);
break;
case "tomorrow-afternoon":
date.setDate(date.getDate() + 1);
date.setHours(13, 0, 0, 0);
break;
case "monday":
const daysUntilMonday = (8 - date.getDay()) % 7 || 7;
date.setDate(date.getDate() + daysUntilMonday);
date.setHours(8, 0, 0, 0);
break;
}
confirmScheduleSend(date);
toggleScheduleMenu();
}
function openCustomSchedule() {
toggleScheduleMenu();
const today = new Date().toISOString().split("T")[0];
document.getElementById("schedule-date").min = today;
document.getElementById("schedule-date").value = today;
document.getElementById("schedule-modal").showModal();
}
function closeScheduleModal() {
document.getElementById("schedule-modal").close();
}
function confirmSchedule() {
const date = document.getElementById("schedule-date").value;
const time = document.getElementById("schedule-time").value;
const scheduledDate = new Date(`${date}T${time}`);
confirmScheduleSend(scheduledDate);
closeScheduleModal();
}
function confirmScheduleSend(date) {
const form = document.getElementById("compose-form");
const input = document.createElement("input");
input.type = "hidden";
input.name = "scheduled_at";
input.value = date.toISOString();
form.appendChild(input);
prepareSubmit();
form.requestSubmit();
}
function prepareSubmit() {
const body = document.getElementById("compose-body").innerHTML;
document.getElementById("compose-body-hidden").value = body;
}
function formatText(command) {
document.execCommand(command, false, null);
document.getElementById("compose-body").focus();
}
function openTemplates() {
document.getElementById("templates-modal").showModal();
}
function closeTemplates() {
document.getElementById("templates-modal").close();
}
function openSignatures() {
document.getElementById("signatures-modal").showModal();
}
function closeSignatures() {
document.getElementById("signatures-modal").close();
}
function openRules() {
document.getElementById("rules-modal").showModal();
}
function closeRules() {
document.getElementById("rules-modal").close();
}
function openAutoResponder() {
document.getElementById("autoresponder-modal").showModal();
}
function closeAutoResponder() {
document.getElementById("autoresponder-modal").close();
}
function saveAutoResponder() {
htmx.trigger("#autoresponder-form", "submit");
closeAutoResponder();
window.showNotification("Auto-reply settings saved", "success");
}
function openLabelManager() {
window.showNotification("Label manager coming soon", "info");
}
function toggleSelectAll(checkbox) {
const items = document.querySelectorAll(
'.mail-item input[type="checkbox"]',
);
items.forEach((item) => {
item.checked = checkbox.checked;
if (checkbox.checked) {
selectedEmails.add(item.dataset.id);
} else {
selectedEmails.delete(item.dataset.id);
}
});
updateBulkActions();
}
function updateBulkActions() {
const bulkBar = document.getElementById("bulk-actions");
if (selectedEmails.size > 0) {
bulkBar.style.display = "flex";
bulkBar.querySelector(".selected-count").textContent =
`${selectedEmails.size} selected`;
} else {
bulkBar.style.display = "none";
}
}
function refreshMailList() {
htmx.trigger(`[data-folder="${currentFolder}"]`, "click");
}
function insertSignature() {
fetch("/api/email/signatures/default")
.then((r) => r.json())
.then((sig) => {
if (sig.content_html) {
const body = document.getElementById("compose-body");
body.innerHTML += "<br><br>" + sig.content_html;
}
});
}
function showTemplateSelector() {
openTemplates();
}
function attachFile() {
const input = document.createElement("input");
input.type = "file";
input.multiple = true;
input.onchange = (e) => {
Array.from(e.target.files).forEach((file) => {
const container = document.getElementById(
"compose-attachments",
);
const chip = document.createElement("div");
chip.className = "attachment-chip";
chip.innerHTML = `
<span>${file.name}</span>
<button type="button" onclick="this.parentElement.remove()">×</button>
`;
container.appendChild(chip);
});
};
input.click();
}
function insertLink() {
const url = prompt("Enter URL:");
if (url) {
document.execCommand("createLink", false, url);
}
}
function insertImage() {
const url = prompt("Enter image URL:");
if (url) {
document.execCommand("insertImage", false, url);
}
}
function saveDraft() {
prepareSubmit();
const form = document.getElementById("compose-form");
const formData = new FormData(form);
fetch("/api/email/draft", {
method: "POST",
body: formData,
}).then(() => {
window.showNotification("Draft saved", "success");
});
}
function createNewTemplate() {
window.showNotification("Template editor coming soon", "info");
}
function createNewSignature() {
window.showNotification("Signature editor coming soon", "info");
}
function createNewRule() {
window.showNotification("Rule editor coming soon", "info");
}
function archiveSelected() {
window.showNotification(
`${selectedEmails.size} emails archived`,
"success",
);
selectedEmails.clear();
updateBulkActions();
refreshMailList();
}
function markAsRead() {
window.showNotification(
`${selectedEmails.size} emails marked as read`,
"success",
);
selectedEmails.clear();
updateBulkActions();
refreshMailList();
}
function addLabelToSelected() {
window.showNotification("Label picker coming soon", "info");
}
function deleteSelected() {
if (confirm(`Delete ${selectedEmails.size} emails?`)) {
window.showNotification(
`${selectedEmails.size} emails deleted`,
"success",
);
selectedEmails.clear();
updateBulkActions();
refreshMailList();
}
}
function openAddAccount() {
document.getElementById("account-modal").showModal();
}
function closeAddAccount() {
document.getElementById("account-modal").close();
}
function saveAccount() {
htmx.trigger("#account-form", "submit");
closeAddAccount();
window.showNotification("Email account added", "success");
}
// Initialize folder click handlers
document.querySelectorAll(".nav-item[data-folder]").forEach((item) => {
item.addEventListener("click", function () {
document
.querySelectorAll(".nav-item")
.forEach((i) => i.classList.remove("active"));
this.classList.add("active");
currentFolder = this.dataset.folder;
});
});
// Load inbox on init
document.addEventListener("DOMContentLoaded", function () {
const inboxItem = document.querySelector(
'.nav-item[data-folder="inbox"]',
);
if (inboxItem) {
htmx.trigger(inboxItem, "click");
}
});
</script>