Files
ACORE/templates/pages/email.html
T

305 lines
18 KiB
HTML

{# Rainloop-style webmail layout #}
{% load i18n %}
{% if not mail_configured %}
<div class="page-content">
<div class="mail-notice">{{ mail_error }}</div>
</div>
{% elif mail_state == 'connect' %}
<div class="page-content">
{% include "partials/email-connect-panel.html" %}
</div>
{% else %}
<div class="mail-app">
<header class="mail-toolbar">
<div class="mail-toolbar-left">
{% if mailboxes|length > 1 %}
<form class="mail-toolbar-switch"
hx-post="/email/switch/"
hx-target="#main-content"
hx-indicator="#loader"
hx-trigger="change">
{% csrf_token %}
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 7L2 7"/></svg>
<select name="mailbox_id" class="mail-toolbar-select" title="{% trans "Почтовый ящик" %}" aria-label="{% trans "Почтовый ящик" %}">
{% for box in mailboxes %}
<option value="{{ box.uuid }}"{% if box.is_active %} selected{% endif %}>{{ box.email }}</option>
{% endfor %}
</select>
</form>
{% else %}
<span class="mail-toolbar-brand" title="{{ mailbox }}">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 7L2 7"/></svg>
<span class="mail-toolbar-email">{{ mailbox }}</span>
</span>
{% endif %}
<a href="/email/?add=1"
hx-get="/email/?add=1"
hx-target="#main-content" hx-push-url="/email/?add=1" hx-indicator="#loader"
class="mail-btn mail-btn-ghost" title="{% trans "Подключить ещё один ящик" %}">{% trans "Добавить" %}</a>
<a href="/email/?view=compose&folder={{ active_folder }}"
hx-get="/email/?view=compose&folder={{ active_folder }}"
hx-target="#main-content" hx-push-url="/email/?view=compose&folder={{ active_folder }}" hx-indicator="#loader"
class="mail-btn mail-btn-compose">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>
{% trans "Написать" %}
</a>
</div>
<form class="mail-toolbar-search" method="get" action="/email/"
hx-get="/email/"
hx-target="#main-content"
hx-push-url="true"
hx-indicator="#loader"
hx-trigger="submit, keyup changed delay:450ms from:input[name='q'], search from:input[name='q']">
<input type="hidden" name="folder" value="{{ active_folder }}">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
<input type="search" name="q" value="{{ search_q|default:'' }}"
placeholder="{% trans "Тема, email, домен…" %}"
autocomplete="off"
enterkeyhint="search">
</form>
<div class="mail-toolbar-right">
<a href="/email/?folder={{ active_folder }}{% if search_q %}&q={{ search_q|urlencode }}{% endif %}"
hx-get="/email/?folder={{ active_folder }}{% if search_q %}&q={{ search_q|urlencode }}{% endif %}"
hx-target="#main-content" hx-push-url="true" hx-indicator="#loader"
class="mail-btn mail-btn-ghost" title="{% trans "Обновить" %}">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-9-9c2.5 0 4.8 1 6.5 2.7L21 8"/><path d="M21 3v5h-5"/></svg>
</a>
<form hx-post="/email/disconnect/" hx-target="#main-content" hx-indicator="#loader" style="margin:0;">
{% csrf_token %}
<button type="submit" class="mail-btn mail-btn-ghost" title="{% trans "Выйти из ящика" %}">{% trans "Выйти" %}</button>
</form>
</div>
</header>
<div class="mail-layout{% if compose %} mail-layout-compose{% endif %}">
{# ── Folders ── #}
<nav class="mail-folders" aria-label="Папки">
{% for key, label in folder_choices %}
<a href="/email/?folder={{ key }}{% if search_q %}&q={{ search_q|urlencode }}{% endif %}"
hx-get="/email/?folder={{ key }}{% if search_q %}&q={{ search_q|urlencode }}{% endif %}" hx-target="#main-content" hx-push-url="/email/?folder={{ key }}{% if search_q %}&q={{ search_q|urlencode }}{% endif %}" hx-indicator="#loader"
class="mail-folder{% if active_folder == key %} is-active{% endif %}">
<span class="mail-folder-icon">
{% if key == 'inbox' %}
<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.11 2 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>
{% elif key == 'sent' %}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/></svg>
{% elif key == 'drafts' %}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 20h9"/><path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"/></svg>
{% elif key == 'trash' %}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/></svg>
{% else %}
<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>
{% endif %}
</span>
<span class="mail-folder-label">{{ label }}</span>
{% if key == 'inbox' and unread_count %}
<span class="mail-folder-badge">{{ unread_count }}</span>
{% endif %}
</a>
{% endfor %}
</nav>
{# ── Message list ── #}
<section class="mail-list" aria-label="Список листів">
<div class="mail-list-head">
<span>
{% for key, label in folder_choices %}{% if active_folder == key %}{{ label }}{% endif %}{% endfor %}
{% if unread_count and active_folder == 'inbox' %}<em>{{ unread_count }}</em>{% endif %}
</span>
</div>
<div class="mail-list-scroll">
{% if messages %}
{% for msg in messages %}
{% include "partials/email-list-row.html" %}
{% endfor %}
{% if mail_has_more %}
<div id="mail-load-more-wrap" class="mail-load-more-wrap">
<button type="button"
class="mail-btn mail-btn-ghost mail-load-more-btn"
hx-get="/email/more/?folder={{ active_folder }}&offset={{ mail_next_offset }}{% if search_q %}&q={{ search_q|urlencode }}{% endif %}"
hx-target="#mail-load-more-wrap"
hx-swap="outerHTML"
hx-indicator="#loader">
Завантажити ще…
</button>
</div>
{% endif %}
{% else %}
<div class="mail-list-empty">
{% if search_q %}Нічого не знайдено.{% else %}Немає листів у цій папці.{% endif %}
</div>
{% endif %}
</div>
</section>
{# ── Reader / Compose ── #}
<section class="mail-reader" aria-label="Лист">
{% if compose %}
<div class="mail-compose">
<div class="mail-reader-head">
<h2>Новий лист</h2>
<a href="/email/?folder={{ active_folder }}"
hx-get="/email/?folder={{ active_folder }}" hx-target="#main-content" hx-push-url="/email/?folder={{ active_folder }}" hx-indicator="#loader"
class="mail-btn mail-btn-ghost">Скасувати</a>
</div>
{% if send_errors %}
<div class="mail-alert mail-alert-error">{% for err in send_errors %}<div>{{ err }}</div>{% endfor %}</div>
{% endif %}
{% if send_success %}
<div class="mail-alert mail-alert-ok">Лист відправлено.</div>
{% endif %}
<form class="mail-compose-form"
hx-post="/email/send/"
hx-target="#main-content"
hx-encoding="multipart/form-data"
hx-indicator="#loader"
enctype="multipart/form-data">
{% csrf_token %}
<label class="mail-field"><span>Кому</span>
<input type="text" name="to" value="{{ form_to|default:'' }}" required placeholder="email@example.com">
</label>
<label class="mail-field"><span>Копія</span>
<input type="text" name="cc" value="{{ form_cc|default:'' }}">
</label>
<label class="mail-field"><span>Прихована</span>
<input type="text" name="bcc" value="{{ form_bcc|default:'' }}" placeholder="bcc@example.com">
</label>
<label class="mail-field"><span>Тема</span>
<input type="text" name="subject" value="{{ form_subject|default:'' }}" required>
</label>
<textarea name="body" required rows="14" placeholder="Текст листа…">{{ form_body|default:'' }}</textarea>
<div class="mail-attach">
<label class="mail-attach-label">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m21.44 11.05-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>
<span>Прикріпити файли</span>
<input type="file" name="attachments" multiple
class="mail-attach-input"
accept="*/*"
data-mail-attach>
</label>
<div class="mail-attach-hint">До 5 файлів, кожен ≤ 10 МБ</div>
<ul class="mail-attach-list" data-mail-attach-list hidden></ul>
</div>
<div class="mail-compose-actions">
<button type="submit" class="mail-btn mail-btn-compose">Надіслати</button>
</div>
</form>
</div>
{% elif view == 'read' and message %}
<div class="mail-message">
<div class="mail-reader-head">
<h2>{{ message.subject|default:"(без теми)" }}</h2>
</div>
<div class="mail-message-meta">
<div><strong>Від:</strong> {{ message.sender }}</div>
<div><strong>Кому:</strong> {{ message.to }}</div>
{% if message.date %}<div><strong>Дата:</strong> {{ message.date }}</div>{% endif %}
</div>
<div class="mail-message-body">
{% if message.body_html %}
<iframe class="mail-html-frame"
title="{{ message.subject|default:'Лист' }}"
sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin"
referrerpolicy="no-referrer"
scrolling="yes"
srcdoc="{{ message.body_html|force_escape }}"></iframe>
{% elif message.body %}
<div class="mail-message-text">{{ message.body|linebreaksbr }}</div>
{% else %}
<div class="mail-message-text" style="color:var(--color-muted-foreground);">Порожній лист</div>
{% endif %}
</div>
{% if message.attachments %}
<div class="mail-attachments">
<div class="mail-attachments-title">Вкладення</div>
<div class="mail-attachments-list">
{% for att in message.attachments %}
<a href="/email/attachment/?folder={{ active_folder }}&uid={{ message.uid }}&i={{ att.index }}" class="mail-attachment">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m21.44 11.05-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>
{{ att.name }}
{% if att.size %}<span>{{ att.size|filesizeformat }}</span>{% endif %}
</a>
{% endfor %}
</div>
</div>
{% endif %}
{% if send_success %}
<div class="mail-alert mail-alert-ok">Відповідь надіслано.</div>
{% endif %}
{% if send_errors %}
<div class="mail-alert mail-alert-error">{% for err in send_errors %}<div>{{ err }}</div>{% endfor %}</div>
{% endif %}
{% if message.reply_to %}
<form class="mail-reply" method="post" action="/email/send/"
hx-post="/email/send/" hx-target="#main-content" hx-indicator="#loader">
{% csrf_token %}
<input type="hidden" name="to" value="{{ message.reply_to }}">
<input type="hidden" name="subject" value="{{ message.reply_subject }}">
<input type="hidden" name="reply_uid" value="{{ message.uid }}">
<input type="hidden" name="reply_folder" value="{{ active_folder }}">
<div class="mail-reply-label">Відповідь для <strong>{{ message.reply_to }}</strong></div>
<textarea name="body" required rows="4" placeholder="Напишіть відповідь…">{{ form_body|default:'' }}</textarea>
<button type="submit" class="mail-btn mail-btn-compose">Надіслати відповідь</button>
</form>
{% endif %}
</div>
{% elif view == 'read' and read_error %}
<div class="mail-reader-empty mail-alert mail-alert-error">{{ read_error }}</div>
{% elif send_success %}
<div class="mail-reader-empty">
<div class="mail-alert mail-alert-ok" style="max-width:20rem;">Лист відправлено.</div>
</div>
{% else %}
<div class="mail-reader-empty">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" opacity="0.35"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 7L2 7"/></svg>
<p>Оберіть лист зі списку</p>
</div>
{% endif %}
</section>
</div>
</div>
<script>
(function () {
function formatSize(n) {
if (n < 1024) return n + ' B';
if (n < 1048576) return (n / 1024).toFixed(1) + ' КБ';
return (n / 1048576).toFixed(1) + ' МБ';
}
function bindAttach(root) {
root.querySelectorAll('[data-mail-attach]').forEach(function (input) {
if (input.dataset.bound) return;
input.dataset.bound = '1';
input.addEventListener('change', function () {
var list = input.closest('.mail-attach') && input.closest('.mail-attach').querySelector('[data-mail-attach-list]');
if (!list) return;
list.innerHTML = '';
var files = Array.prototype.slice.call(input.files || []);
if (!files.length) { list.hidden = true; return; }
files.forEach(function (f) {
var li = document.createElement('li');
li.textContent = f.name + ' ';
var span = document.createElement('span');
span.textContent = formatSize(f.size);
li.appendChild(span);
list.appendChild(li);
});
list.hidden = false;
});
});
}
bindAttach(document);
document.body.addEventListener('htmx:afterSwap', function (e) {
bindAttach(e.target || document);
});
})();
</script>
{% endif %}