summaryrefslogtreecommitdiff
path: root/templates
diff options
context:
space:
mode:
Diffstat (limited to 'templates')
-rw-r--r--templates/auth/login.django36
-rw-r--r--templates/error.django10
-rw-r--r--templates/layouts/generic.django17
-rw-r--r--templates/layouts/mailbox.django17
-rw-r--r--templates/layouts/main.django59
-rw-r--r--templates/mail/folder.django5
-rw-r--r--templates/partials/filters.django129
-rw-r--r--templates/partials/footer.django3
-rw-r--r--templates/partials/navbar.django124
-rw-r--r--templates/partials/pane.django32
-rw-r--r--templates/partials/preview.django46
-rw-r--r--templates/partials/sidebar.django28
12 files changed, 476 insertions, 30 deletions
diff --git a/templates/auth/login.django b/templates/auth/login.django
index 33ed81a..355c7ba 100644
--- a/templates/auth/login.django
+++ b/templates/auth/login.django
@@ -1,25 +1,27 @@
{% extends 'layouts/generic.django' %}
{% block content %}
- <div class="login-container">
- <h1>{{ AppName }}</h1>
- <p class="subtitle">{{ AppDescription }}</p>
+ <div class="login-page">
+ <div class="login-container">
+ <h1>{{ AppName }}</h1>
+ <p class="subtitle">{{ AppDescription }}</p>
- {% if Error %}
- <div class="error">{{ Error }}</div>
- {% endif %}
+ {% if Error %}
+ <div class="error">{{ Error }}</div>
+ {% endif %}
- <form method="POST" action="{% url 'auth.login' %}">
- <div class="field">
- <label>Email</label>
- <input type="email" name="email" required autofocus />
- </div>
+ <form method="POST" action="{% url 'auth.login' %}">
+ <div class="field">
+ <label>Email</label>
+ <input type="email" name="email" required autofocus />
+ </div>
- <div class="field">
- <label>Password</label>
- <input type="password" name="password" required />
- </div>
+ <div class="field">
+ <label>Password</label>
+ <input type="password" name="password" required />
+ </div>
- <button type="submit">Login</button>
- </form>
+ <button type="submit">Login</button>
+ </form>
+ </div>
</div>
{% endblock %}
diff --git a/templates/error.django b/templates/error.django
index f766688..d38b972 100644
--- a/templates/error.django
+++ b/templates/error.django
@@ -1,8 +1,10 @@
{% extends 'layouts/generic.django' %}
{% block content %}
- <div class="error-container">
- <h1>{{ ErrorTitle }}</h1>
- <p>{{ ErrorMessage }}</p>
- <a href="{% url 'auth.login' %}">Go back home</a>
+ <div class="error-page">
+ <div class="error-container">
+ <h1>{{ ErrorTitle }}</h1>
+ <p>{{ ErrorMessage }}</p>
+ <a href="{% url 'auth.login' %}">Go back home</a>
+ </div>
</div>
{% endblock %}
diff --git a/templates/layouts/generic.django b/templates/layouts/generic.django
index 3ca183b..3222516 100644
--- a/templates/layouts/generic.django
+++ b/templates/layouts/generic.django
@@ -2,24 +2,23 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
- <title>{{ Title }} - {{ Appname }}</title>
+ <title>{{ Title }}</title>
<link rel="stylesheet" href="/static/css/main.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <link rel="manifest" href="/static/extra/site.webmanifest" />
{% block head %}
{% endblock %}
</head>
<body>
- <main class="content">
- {% block content %}
+ {% block body %}
+ <main class="content">
+ {% block content %}
- {% endblock %}
- </main>
+ {% endblock %}
+ </main>
+ {% endblock %}
- <footer>
- {{ AppName }} - Powered by {{ AppEngine }} - &copy; <a href="https://shi.foo" target="_blank" rel="noopener noreferrer">shi.foo</a> 2025
- </footer>
+ {% include 'partials/footer.django' %}
</body>
{% block scripts %}
diff --git a/templates/layouts/mailbox.django b/templates/layouts/mailbox.django
new file mode 100644
index 0000000..ebb0352
--- /dev/null
+++ b/templates/layouts/mailbox.django
@@ -0,0 +1,17 @@
+{% extends 'layouts/main.django' %}
+
+{% block content %}
+ <div class="mailbox">
+ <aside class="sidebar">
+ {% include 'partials/sidebar.django' %}
+ </aside>
+
+ <section class="pane">
+ {% include 'partials/pane.django' %}
+ </section>
+
+ <section class="preview">
+ {% include 'partials/preview.django' %}
+ </section>
+ </div>
+{% endblock %}
diff --git a/templates/layouts/main.django b/templates/layouts/main.django
new file mode 100644
index 0000000..929f656
--- /dev/null
+++ b/templates/layouts/main.django
@@ -0,0 +1,59 @@
+{% extends 'layouts/generic.django' %}
+
+{% block body %}
+ {% include 'partials/navbar.django' %}
+ <main>
+ {% block content %}
+
+ {% endblock %}
+ </main>
+{% endblock %}
+
+{% block scripts %}
+ <script>
+ document.addEventListener('DOMContentLoaded', function () {
+ // Handle dropdown clicks
+ document.querySelectorAll('.options-subitem > a').forEach(function (item) {
+ item.addEventListener('click', function (e) {
+ e.preventDefault()
+ const parent = this.parentElement
+
+ if (parent.classList.contains('disabled')) {
+ return
+ }
+
+ document.querySelectorAll('.options-subitem.open').forEach(function (other) {
+ if (other !== parent) {
+ other.classList.remove('open')
+ }
+ })
+
+ parent.classList.toggle('open')
+ })
+ })
+
+ document.addEventListener('click', function (e) {
+ if (!e.target.closest('.options-subitem')) {
+ document.querySelectorAll('.options-subitem.open').forEach(function (item) {
+ item.classList.remove('open')
+ })
+ }
+ })
+
+ // Toggle search filters
+ const toggleBtn = document.getElementById('toggle-filters')
+ const filters = document.getElementById('search-filters')
+
+ if (toggleBtn && filters) {
+ toggleBtn.addEventListener('click', function (e) {
+ e.preventDefault()
+ if (filters.style.display === 'none') {
+ filters.style.display = 'block'
+ } else {
+ filters.style.display = 'none'
+ }
+ })
+ }
+ })
+ </script>
+{% endblock %}
diff --git a/templates/mail/folder.django b/templates/mail/folder.django
new file mode 100644
index 0000000..4cdce72
--- /dev/null
+++ b/templates/mail/folder.django
@@ -0,0 +1,5 @@
+{% extends 'layouts/mailbox.django' %}
+
+{% block scripts %}
+ <script src="{% static 'js/filters.js' %}"></script>
+{% endblock %}
diff --git a/templates/partials/filters.django b/templates/partials/filters.django
new file mode 100644
index 0000000..98765be
--- /dev/null
+++ b/templates/partials/filters.django
@@ -0,0 +1,129 @@
+<div class="filters" id="filters" style="display: none;">
+ <div class="filter-header">
+ <span>Advanced Search</span>
+ <button type="button" class="close-filters" id="close-filters">×</button>
+ </div>
+
+ <div class="filter-section">
+ <div class="filter-label-row">
+ <label>From</label>
+ <select name="from_operator">
+ <option value="includes">includes</option>
+ <option value="excludes">excludes</option>
+ <option value="equals">equals</option>
+ </select>
+ </div>
+ <div class="tag-input-container">
+ <div class="tags" id="from-tags"></div>
+ <input type="text" class="tag-input" id="from-input" placeholder="Add email..." autocomplete="off" />
+ <input type="hidden" name="from[]" id="from-hidden" />
+ </div>
+ </div>
+
+ <div class="filter-section">
+ <div class="filter-label-row">
+ <label>To</label>
+ <select name="to_operator">
+ <option value="includes">includes</option>
+ <option value="excludes">excludes</option>
+ <option value="equals">equals</option>
+ </select>
+ </div>
+ <div class="tag-input-container">
+ <div class="tags" id="to-tags"></div>
+ <input type="text" class="tag-input" id="to-input" placeholder="Add email..." autocomplete="off" />
+ <input type="hidden" name="to[]" id="to-hidden" />
+ </div>
+ </div>
+
+ <div class="filter-section">
+ <div class="filter-label-row">
+ <label>Subject</label>
+ <select name="subject_operator">
+ <option value="includes">includes</option>
+ <option value="excludes">excludes</option>
+ <option value="equals">equals</option>
+ <option value="startswith">starts</option>
+ <option value="endswith">ends</option>
+ </select>
+ </div>
+ <input type="text" name="subject" class="filter-input" placeholder="Search..." />
+ </div>
+
+ <div class="filter-section">
+ <div class="filter-label-row">
+ <label>Body</label>
+ <select name="body_operator">
+ <option value="contains">contains</option>
+ <option value="excludes">excludes</option>
+ </select>
+ </div>
+ <input type="text" name="body" class="filter-input" placeholder="Search..." />
+ </div>
+
+ <div class="filter-section">
+ <div class="filter-label-row">
+ <label>Attachments</label>
+ <select name="filename_operator">
+ <option value="includes">includes</option>
+ <option value="excludes">excludes</option>
+ <option value="equals">equals</option>
+ </select>
+ </div>
+ <div class="tag-input-container">
+ <div class="tags" id="filename-tags"></div>
+ <input type="text" class="tag-input" id="filename-input" placeholder="Add filename..." autocomplete="off" />
+ <input type="hidden" name="filename[]" id="filename-hidden" />
+ </div>
+ </div>
+
+ <div class="filter-section">
+ <div class="filter-label-row">
+ <label>Flags</label>
+ </div>
+ <div class="flag-checkboxes">
+ <label class="flag-option"><input type="checkbox" name="has_attachment" value="1" /><span>Attachment</span></label>
+ <label class="flag-option"><input type="checkbox" name="is_flagged" value="1" /><span>Flagged</span></label>
+ <label class="flag-option"><input type="checkbox" name="is_unread" value="1" /><span>Unread</span></label>
+ <label class="flag-option"><input type="checkbox" name="is_answered" value="1" /><span>Answered</span></label>
+ </div>
+ </div>
+
+ <div class="filter-section">
+ <div class="filter-label-row">
+ <label>Date</label>
+ </div>
+ <select name="date_preset" class="filter-input" id="date-preset">
+ <option value="">Any time</option>
+ <option value="today">Today</option>
+ <option value="yesterday">Yesterday</option>
+ <option value="last7days">Last 7 days</option>
+ <option value="last30days">Last 30 days</option>
+ <option value="custom">Custom range</option>
+ </select>
+ <div id="custom-date-range" style="display: none; margin-top: 5px;">
+ <input type="date" name="date_from" style="margin-bottom: 4px;" />
+ <input type="date" name="date_to" />
+ </div>
+ </div>
+
+ <div class="filter-section">
+ <div class="filter-label-row">
+ <label>Search In</label>
+ </div>
+ <select name="scope" class="filter-input" id="scope-select">
+ <option value="current">Current folder</option>
+ <option value="recursive">Current + subfolders</option>
+ <option value="all">All folders</option>
+ <option value="custom">Custom folders</option>
+ </select>
+ <input type="text" name="custom_folders" class="filter-input" placeholder="Folder1, Folder2..." style="margin-top: 5px; display: none;" id="custom-folders-input" />
+ </div>
+
+ <div class="filter-actions">
+ <button type="button" class="btn-clear" id="clear-filters">Clear</button>
+ <button type="submit" class="btn-apply">Apply</button>
+ </div>
+</div>
+
+<div class="autocomplete-dropdown" id="autocomplete-dropdown" style="display: none;"></div>
diff --git a/templates/partials/footer.django b/templates/partials/footer.django
new file mode 100644
index 0000000..3900a0c
--- /dev/null
+++ b/templates/partials/footer.django
@@ -0,0 +1,3 @@
+<footer>
+ {{ AppName }} - Powered by {{ AppEngine }} - &copy; <a href="https://shi.foo" target="_blank" rel="noopener noreferrer">shi.foo</a> 2025
+</footer>
diff --git a/templates/partials/navbar.django b/templates/partials/navbar.django
new file mode 100644
index 0000000..7b18bc9
--- /dev/null
+++ b/templates/partials/navbar.django
@@ -0,0 +1,124 @@
+<nav class="navbar">
+ <nav class="topnav">
+ <div class="nav-title">
+ <div class="nav-item">
+ <a href="{% url 'mail.inbox' %}">{{ AppName }}</a>
+ </div>
+ </div>
+ <div class="nav-links">
+ <div class="nav-item">
+ <a href="{% url 'mail.inbox' %}">Mail</a>
+ </div>
+ <div class="nav-item">
+ <a href="#">Contacts</a>
+ </div>
+ <div class="nav-item">
+ <a href="#">Calendar</a>
+ </div>
+ <div class="nav-item">
+ <a href="#">Settings</a>
+ </div>
+ <div class="nav-item">
+ <a href="{% url 'auth.logout' %}">Logout</a>
+ </div>
+ </div>
+ </nav>
+ <nav class="subnav">
+ <div class="nav-links">
+ <div class="nav-subitem">
+ <a href="{% url 'mail.inbox' %}">Refresh</a>
+ </div>
+ <div class="nav-subitem">
+ <a href="#">Compose</a>
+ </div>
+ <div class="nav-subitem disabled">
+ <a href="#">Reply</a>
+ </div>
+ <div class="nav-subitem disabled">
+ <a href="#">Reply All</a>
+ </div>
+ <div class="nav-subitem disabled options-subitem">
+ <a href="#">Forward</a>
+ <div class="options-dropdown">
+ <div class="options-dropdown-item">
+ <a href="#">Forward as Attachment</a>
+ </div>
+ <div class="options-dropdown-item">
+ <a href="#">Forward Inline</a>
+ </div>
+ <div class="options-dropdown-item">
+ <a href="#">Resend (bounce)</a>
+ </div>
+ </div>
+ </div>
+ <div class="nav-subitem disabled">
+ <a href="#">Delete</a>
+ </div>
+ <div class="nav-subitem disabled">
+ <a href="#">Archive</a>
+ </div>
+ <div class="nav-subitem disabled">
+ <a href="#">Junk</a>
+ </div>
+ <div class="nav-subitem disabled options-subitem">
+ <a href="#">Mark</a>
+ <div class="options-dropdown">
+ <div class="options-dropdown-item">
+ <a href="#">As Read</a>
+ </div>
+ <div class="options-dropdown-item">
+ <a href="#">As Unread</a>
+ </div>
+ <div class="options-dropdown-item">
+ <a href="#">As Flagged</a>
+ </div>
+ <div class="options-dropdown-item">
+ <a href="#">As Unflagged</a>
+ </div>
+ </div>
+ </div>
+ <div class="nav-subitem disabled options-subitem">
+ <a href="#">More</a>
+ <div class="options-dropdown">
+ <div class="options-dropdown-item">
+ <a href="#">Print this message</a>
+ </div>
+ <div class="options-dropdown-item flyout-subitem">
+ <a href="#">Download</a>
+ <div class="flyout-dropdown">
+ <div class="flyout-dropdown-item">
+ <a href="#">Source (.eml)</a>
+ </div>
+ <div class="flyout-dropdown-item">
+ <a href="#">Mbox (as .zip)</a>
+ </div>
+ <div class="flyout-dropdown-item">
+ <a href="#">Maildir (as .zip)</a>
+ </div>
+ </div>
+ </div>
+ <div class="options-dropdown-item">
+ <a href="#">Edit as New</a>
+ </div>
+ <div class="options-dropdown-item">
+ <a href="#">View Source</a>
+ </div>
+ <div class="options-dropdown-item">
+ <a href="#">Open in New Window</a>
+ </div>
+ <div class="options-dropdown-item">
+ <a href="#">Create Filter</a>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="nav-search">
+ <form action="#" method="get">
+ <input type="text" name="q" placeholder="Search" class="search-input" />
+ <button type="button" id="toggle-filters" class="btn-filters">Filters</button>
+ <button type="submit" class="btn-search">Search</button>
+ {% include 'partials/filters.django' %}
+ </form>
+ </div>
+ </nav>
+</nav>
diff --git a/templates/partials/pane.django b/templates/partials/pane.django
new file mode 100644
index 0000000..873e9de
--- /dev/null
+++ b/templates/partials/pane.django
@@ -0,0 +1,32 @@
+<div class="pane">
+ {% if Emails %}
+ {% for email in Emails %}
+ <div class="email-row {% if not email.IsRead %}unread{% endif %} {% if email.Active %}active{% endif %}" data-email-id="{{ email.ID }}">
+ <div class="email-checkbox">
+ <input type="checkbox" />
+ </div>
+
+ <div class="email-flag {% if email.IsFlagged %}flagged{% endif %}">
+ {% comment %} <img src="{% static 'icons/flag.svg' %}" alt="Flag" /> (We will write the static filter later){% endcomment %}
+ </div>
+
+ <div class="email-info">
+ <div class="email-from">{{ email.FromName }}</div>
+ <div class="email-subject">{{ email.Subject }}</div>
+ <div class="email-preview">{{ email.Preview }}</div>
+ </div>
+
+ <div class="email-meta">
+ {% if email.HasAttachments %}
+ <span class="attachment-icon">📎</span>
+ {% endif %}
+ <span class="email-date">{{ email.Date }}</span>
+ </div>
+ </div>
+ {% endfor %}
+ {% else %}
+ <div class="empty-state">
+ <p>No emails in this folder</p>
+ </div>
+ {% endif %}
+</div>
diff --git a/templates/partials/preview.django b/templates/partials/preview.django
new file mode 100644
index 0000000..dd40109
--- /dev/null
+++ b/templates/partials/preview.django
@@ -0,0 +1,46 @@
+{% if Email %}
+ <div class="email-header">
+ <h2 class="email-subject">{{ Email.Subject }}</h2>
+
+ <div class="email-actions">
+ <button class="btn-icon" title="Reply">↶</button>
+ <button class="btn-icon" title="Reply All">⇄</button>
+ <button class="btn-icon" title="Forward">→</button>
+ <button class="btn-icon" title="Archive">▼</button>
+ <button class="btn-icon" title="Delete">×</button>
+ </div>
+ </div>
+
+ <div class="email-sender">
+ <div class="sender-info">
+ <strong>{{ Email.FromName }}</strong> &lt;{{ Email.FromAddress }}&gt;
+ </div>
+ <div class="email-date">{{ Email.Date }}</div>
+ </div>
+
+ <div class="email-recipients">
+ <div>
+ <strong>To:</strong> {{ Email.To }}
+ </div>
+ {% if Email.Cc %}
+ <div>
+ <strong>Cc:</strong> {{ Email.Cc }}
+ </div>
+ {% endif %}
+ </div>
+
+ {% if Email.Attachments %}
+ <div class="email-attachments">
+ <strong>Attachments:</strong>
+ {% for attachment in Email.Attachments %}
+ <a href="#" class="attachment">{{ attachment.Filename }} ({{ attachment.Size }})</a>
+ {% endfor %}
+ </div>
+ {% endif %}
+
+ <div class="email-body">{{ Email.Body|safe }}</div>
+{% else %}
+ <div class="no-email-selected">
+ <p>Select an email to view</p>
+ </div>
+{% endif %}
diff --git a/templates/partials/sidebar.django b/templates/partials/sidebar.django
new file mode 100644
index 0000000..ea0a72d
--- /dev/null
+++ b/templates/partials/sidebar.django
@@ -0,0 +1,28 @@
+<ul class="folder-list">
+ {% for folder in Folders %}
+ <li class="folder-item {% if folder.Active %}active{% endif %}">
+ <a href="/mail/{{ folder.IMAPName|lower }}">
+ <img src="{{ folder.Icon }}" alt="{{ folder.Name }}" class="folder-icon" />
+ <span class="folder-name">{{ folder.Name }}</span>
+ {% if folder.UnreadCount > 0 %}
+ <span class="folder-count">{{ folder.UnreadCount }}</span>
+ {% endif %}
+ </a>
+ {% if folder.Subfolders %}
+ <ul class="subfolder-list">
+ {% for subfolder in folder.Subfolders %}
+ <li class="folder-item subfolder {% if subfolder.Active %}active{% endif %}">
+ <a href="/mail/{{ subfolder.IMAPName|lower }}">
+ <img src="{{ subfolder.Icon }}" alt="{{ subfolder.Name }}" class="folder-icon" />
+ <span class="folder-name">{{ subfolder.Name }}</span>
+ {% if subfolder.UnreadCount > 0 %}
+ <span class="folder-count">{{ subfolder.UnreadCount }}</span>
+ {% endif %}
+ </a>
+ </li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ </li>
+ {% endfor %}
+</ul>