themes/alps: new theme

This commit is contained in:
Simon Ser 2020-03-19 18:03:13 +01:00
parent 9eac0b453a
commit c182fbde63
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
11 changed files with 650 additions and 0 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
/koushin
/themes/*
!/themes/sourcehut
!/themes/alps
/plugins/*
!/plugins/base
!/plugins/caldav

View File

@ -0,0 +1,24 @@
(function() {
var sheet = document.styleSheets[0];
var addCSSRule = function(selector, rules, index) {
if ("insertRule" in sheet) {
sheet.insertRule(selector + "{" + rules + "}", index);
return;
}
if ("addRule" in sheet) {
sheet.addRule(selector, rules, index);
}
};
var checkboxAll = document.getElementById("action-checkbox-all");
if (checkboxAll) {
addCSSRule(".message-list-checkbox", "display: table-cell !important;");
checkboxAll.addEventListener("click", function(ev) {
var allChecked = this.checked;
var inputs = document.querySelectorAll("tr .message-list-checkbox input");
Array.prototype.slice.apply().forEach(function(cb) {
cb.checked = allChecked;
});
});
}
})();

View File

@ -0,0 +1,269 @@
/* Box sizing rules */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Remove default padding */
ul[class],
ol[class] {
padding: 0;
}
/* Remove default margin */
body,
h1,
h2,
h3,
h4,
p,
ul[class],
ol[class],
li,
figure,
figcaption,
blockquote,
dl,
dd {
margin: 0;
}
/* Set core body defaults */
body {
min-height: 100vh;
scroll-behavior: smooth;
text-rendering: optimizeSpeed;
line-height: 1.5;
}
/* Remove list styles on ul, ol elements with a class attribute */
ul[class],
ol[class] {
list-style: none;
}
/* A elements that don't have a class get default styles */
a:not([class]) {
text-decoration-skip-ink: auto;
}
/* Make images easier to work with */
img {
max-width: 100%;
display: block;
}
/* Natural flow and rhythm in articles by default */
article > * + * {
margin-top: 1em;
}
/* Inherit fonts for inputs and buttons */
input,
button,
textarea,
select {
font: inherit;
}
/* Remove all animations and transitions for people that prefer not to see them */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/** { box-sizing: content-box; }*/
body { font-family: arial,sans-serif; font-size: 13px; padding: 0; margin: 0;
min-width: 1024px; min-height: 100vh; display: flex; flex-direction: column;
}
input[type="text"],
input[type="email"],
input[type="password"],
input[type="file"],
textarea {
margin: 0;
border: none;
border: 1px solid #e0e0e0;
box-shadow: inset 1px 1px 0 #f8f8f8;
border-radius: 2px;
font-size: 13px;
padding: 0.3rem 0.5rem 0.3rem 0.5rem;
background-color: white;
}
.page-wrap {
display: flex;
flex: 1 100%;
flex-direction: row;
}
a { color: #15c; }
button { font-size: 1rem; }
header { border-bottom: 1px solid #e0e0e0;}
header nav,
footer { padding: 0.5rem 1rem 0.5rem 0.5rem; }
header nav { min-height: 1rem; }
/*header nav strong { color: #555; }*/
header nav > a { margin-right: 1rem; }
header nav span { color: #757373; }
header nav div { float: right; }
header nav div > a{ margin-left: 1rem; }
header a.active { font-weight: bold; color: black; text-decoration: none; }
footer { text-align: right; }
.actions { padding: 0.5rem; }
.container { flex: 1 auto; display: flex; flex-direction: column; flex-wrap: nowrap; min-width: 0; }
aside { flex: 0 0 180px; }
aside a { width: 100%; display: block; padding: 0.4rem 0 0.4rem 0.5rem; }
aside a.active { font-weight: bold; color: black; text-decoration: none; }
aside img { display: block; }
main { flex: 1 100%; display: flex; flex-direction: column; padding: 0.5rem 1rem 0.5rem 0.5rem; min-width: 0; }
aside a.active,
main { background-color: #f6f6f6; }
aside a.compose-mail.active { color: #008d47; }
aside a.compose-mail.active,
main.compose { background-color: #f6fff6; }
main.compose { flex: 1 auto; padding: 1rem; }
main.compose form { flex: 1 auto; display: flex; flex-direction: column; }
main.compose form label { margin-top: 5px; }
main.compose form label span { display: inline-block; font-weight: bold; min-width: 100px; }
main.compose form input { width: 80%; }
main.compose form textarea { flex: 1 auto; resize: none; margin-top: 1rem; }
main.compose button[type="submit"] { padding: 0.4rem 1rem 0.35rem; font-weight: bold; }
main.compose .actions { text-align: right; }
main table { border-collapse: collapse; width: 100%; border: 1px solid #eee; }
main table td { white-space: nowrap; padding: 0.3rem; color: #757373;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200px;
}
main table tbody tr { border-bottom: 1px solid #eee; }
main table td a { text-decoration: none; }
main table td a:hover { text-decoration: underline; }
main.message pre { flex: 1 auto; padding: 1rem; white-space: pre-wrap; word-break: break-all; background-color: white; border: 1px solid #eee; max-width: 100% }
main.message table { background-color: white; }
main.message th { width: 5%;}
main.message h1 { font-size: 1.2rem; padding: 0.5rem;}
.message-list-subject a { color: #77c; }
.message-list-unread .message-list-sender,
.message-list-unread .message-list-subject,
.message-list-unread .message-list-date { font-weight: bold; }
.message-list-unread .message-list-sender,
.message-list-unread .message-list-date { color: black;}
.message-list-date {
text-align: right;
padding-right: 0.5rem;
}
.message-list-unread .message-list-subject a { color: #00c; }
.message-list-unread {
background-color: white;
border-left: 1px solid #f2f2f2;
border-right: 1px solid #f2f2f2;
}
.message-list-unread td { opacity: 1; }
aside .compose-mail {
color: #008d47;
font-weight: bold;
/*background-color: #f5fcf2;*/
text-decoration: none;
}
main table th {
text-align: left;
padding: 0.3rem; font-weight: normal;
}
main table thead {
border-bottom: 1px solid #e0e0e0;
background-color: white;
}
main table tfoot {
border-top: 1px solid #e0e0e0;
background-color: white;
}
th form { display: inline; }
th input[type="text"] {
flex: 1;
margin: 0;
}
th input[type="text"] + button {
margin-left: -4rem;
width: 4rem;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.actions-wrap { display: flex; flex-direction: row; }
.actions-message { margin-right: 1rem; }
.actions-pagination { margin-left: 1rem; display: flex; flex-direction: row; }
.actions-search { flex: 1 auto; display: flex; flex-direction: row; }
input[type="submit"],
.button,
button,
.button-link {
margin: 0;
cursor: pointer;
text-decoration: none;
text-align: center;
display: inline-block;
/*padding: 0.4rem 0.4rem 0.35rem;*/
padding: 0.3rem 0.3rem 0.25rem;
min-width: 1rem;
font-size: 12px;
border: 1px solid #ddd;
border-radius: 3px;
vertical-align: middle;
}
.button-link {
border: 1px solid transparent;
text-decoration: none;
}
.button-link:hover {
text-decoration: underline;
}
input[type="submit"],
.button,
button {
background-color: #f5f5f5;
border: 1px solid #ddd;
color: black;
}
.button:hover,
button:hover {
background-color: white;
}
.button:active,
button:active {
color: #ccc;
background-color: #f8f8f8;
}
.message-list-checkbox { display: none; }

40
themes/alps/compose.html Normal file
View File

@ -0,0 +1,40 @@
{{template "head.html"}}
{{template "nav.html" .}}
<div class="page-wrap">
<aside>
<a href="/compose" class="compose-mail active">Compose&nbsp;Mail</a>
<a href="/mailbox/INBOX">Inbox</a>
<a href="/mailbox/Drafts">Drafts</a>
<a href="/mailbox/Sent">Sent</a>
<a href="/mailbox/Archive">Archive</a>
<a href="/mailbox/Junk">Junk</a>
<a href="/mailbox/Trash">Trash</a>
</aside>
<div class="container">
<main class="compose">
<form method="post" action="" enctype="multipart/form-data">
<input type="hidden" name="in_reply_to" value="{{.Message.InReplyTo}}">
<label><span>From</span><input type="email" name="from" id="from" value="{{.Message.From}}" /></label>
<label><span>To</span><input type="email" name="to" id="to" value="{{.Message.ToString}}" multiple {{ if not .Message.To }} autofocus{{ end }}/></label>
<label><span>Subject</span><input type="text" name="subject" id="subject" value="{{.Message.Subject}}" {{ if .Message.To }} autofocus{{ end }}/></label>
<label><span>Attachments</span><input type="file" name="attachments" id="attachments" multiple></label>
<textarea name="text" class="body">{{.Message.Text}}</textarea>
<div class="actions">
<button type="submit">Send Message</button>
&nbsp;&nbsp;
<a href="/mailbox/INBOX">Discard</a>
</div>
</form>
</main>
</div>
</div>
{{template "foot.html"}}

3
themes/alps/foot.html Normal file
View File

@ -0,0 +1,3 @@
<script type="text/javascript" src="/themes/alps/assets/helpers.js"></script>
</body>
</html>

10
themes/alps/head.html Normal file
View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<title>Webmail</title>
<link rel="stylesheet" href="/themes/alps/assets/style.css">
</head>
<body>

26
themes/alps/login.html Normal file
View File

@ -0,0 +1,26 @@
{{template "head.html"}}
<h1>koushin webmail</h1>
<form method="post" action="/login">
<p>
<label>
<strong>Username</strong>
<br/>
<input type="text" name="username" id="username" autofocus />
</label>
</p>
<br/>
<p>
<label>
<strong>Password</strong>
<br/>
<input type="password" name="password" id="password" />
</label>
</p>
<br/>
<p>
<button>Sign in</button>
</p>
</form>
{{template "foot.html"}}

66
themes/alps/mailbox.html Normal file
View File

@ -0,0 +1,66 @@
{{template "head.html"}}
{{template "nav.html" . }}
<div class="page-wrap">
<aside>
<!-- the logo image, dimensions 200x32 may be present or not -->
<a href="/compose" class="compose-mail">Compose&nbsp;Mail</a>
{{$current := .Mailbox}}
{{range .Mailboxes}}
<a href="/mailbox/{{.Name | pathescape}}"
{{ if eq $current.Name .Name }}class="active"{{ end }}>
{{ if eq .Name "INBOX" }}
Inbox
{{else}}
{{.Name}}
{{end}}
{{ if eq $current.Name .Name }}
{{ if $current.Unseen }}({{ $current.Unseen }}){{ end }}
{{ end }}
</a>
{{end}}
</aside>
<div class="container">
<form id="messages-form" method="post"></form>
<main>
<table>
<thead>
{{ template "messages-header.html" . }}
</thead>
<tbody>
{{range .Messages}}
<tr data="{{.Flags}}" class="message-list-item {{ if not (.HasFlag "\\Seen") }}message-list-unread{{ end }}">
<td width="1%" class="message-list-checkbox">
<input type="checkbox" name="uids" value="{{.Uid}}" form="messages-form">
</td>
<td width="10%" class="message-list-sender">
{{ range .Envelope.From }}
{{ if .PersonalName }}
{{.PersonalName}}
{{ else }}
{{.MailboxName}}@{{.HostName}}
{{ end }}
{{ end }}
</td>
<td width="80%" class="message-list-subject">
<a href="/message/{{$.Mailbox.Name | pathescape}}/{{.Uid}}?part={{.TextPartName}}">
{{if .Envelope.Subject}}
{{.Envelope.Subject}}
{{else}}
(No subject)
{{end}}
</a>
</td>
<td width="9%" nowrap="" class="message-list-date">{{ .Envelope.Date | formatdate }}</td>
</tr>
{{ end }}
</tbody>
<tfoot>
{{ template "messages-header.html" .}}
</tfoot>
</table>
</main>
</div>
</div>
{{template "foot.html"}}

154
themes/alps/message.html Normal file
View File

@ -0,0 +1,154 @@
{{template "head.html"}}
{{template "nav.html" .}}
<div class="page-wrap">
{{$current := .Mailbox}}
<aside>
<!-- the logo image, dimensions 200x32 may be present or not -->
<a href="/compose" class="compose-mail">Compose&nbsp;Mail</a>
<!-- TODO: get mailbox list from render data -->
<a href="/mailbox/INBOX" {{ if eq $current.Name "INBOX" }}class="active"{{ end }}>Inbox {{ if $current.Unseen }}({{ $current.Unseen }}){{ end }}</a>
<a href="/mailbox/Drafts" {{ if eq $current.Name "Drafts" }}class="active"{{ end }}>Drafts</a>
<a href="/mailbox/Sent" {{ if eq $current.Name "Sent" }}class="active"{{ end }}>Sent</a>
<a href="/mailbox/Archive" {{ if eq $current.Name "Archive" }}class="active"{{ end }}>Archive {{ if $current.Unseen }}({{ $current.Unseen }}){{ end }}</a>
<a href="/mailbox/Junk" {{ if eq $current.Name "Junk" }}class="active"{{ end }}>Junk {{ if $current.Unseen }}({{ $current.Unseen }}){{ end }}</a>
<a href="/mailbox/Trash" {{ if eq $current.Name "Trash" }}class="active"{{ end }}>Trash</a>
</aside>
<div class="container">
<main class="message">
<table>
<tr>
<tr>
<th colspan="2">
<div class="actions-wrap">
<div class="actions-message">
<a href="/mailbox/{{.Mailbox.Name | pathescape}}?page={{.MailboxPage}}" class="button-link">« Back</a>
&nbsp;&nbsp;
{{ if and (ne .Mailbox.Name "Archive") (ne .Mailbox.Name "Drafts") (ne .Mailbox.Name "Sent") }}
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/move">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<input type="hidden" name="to" value="Archive">
<button>Archive</button>
</form>
{{ end }}
{{ if and (ne .Mailbox.Name "INBOX") (ne .Mailbox.Name "Sent") (ne .Mailbox.Name "Drafts") }}
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/move">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<input type="hidden" name="to" value="INBOX">
<button>
{{ if (eq .Mailbox.Name "Junk") }}
Not Spam
{{ else }}
Move to Inbox
{{ end }}
</button>
</form>
{{ end }}
{{ if or (eq .Mailbox.Name "INBOX") (eq .Mailbox.Name "Trash") }}
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/move">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
&nbsp;&nbsp;
<input type="hidden" name="to" value="Junk">
<button>Report Spam</button>
</form>
{{ end }}
{{ if or (eq .Mailbox.Name "Trash") (eq .Mailbox.Name "Junk") }}
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/delete">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<button>Delete Permanently</button>
</form>
{{ else }}
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/move">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
&nbsp;&nbsp;
<input type="hidden" name="to" value="Trash">
<button>Delete</button>
</form>
{{ end }}
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/flag">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<input type="hidden" name="flags" value="\Seen">
<input type="hidden" name="action" value="remove">
<button>Mark&nbsp;Unread</button>
</form>
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/move">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<select name="to">
{{range .Mailboxes}}
<option value="{{.Name}}" {{if eq .Name $.Mailbox.Name}}selected>Move to...{{else}}>{{.Name}}{{ end }}</option>
{{end}}
</select>
<input type="submit" value="Move">
</form>
&nbsp;&nbsp;
<a href="{{.Message.Uid}}/reply?part={{.Part.PathString}}">Reply</a>
&nbsp;&nbsp;
<a href="{{.Message.Uid}}/forward?part={{.Part.PathString}}">Forward</a>
</div>
</div>
</th>
</tr>
</tr>
<tr>
<th colspan="2">
<h1>
{{if .Message.Envelope.Subject}}
{{.Message.Envelope.Subject}}
{{else}}
(No subject)
{{end}}
</h1>
</th>
</tr>
<tr>
<th>From:</th>
<td>{{template "addr-list" .Message.Envelope.From}}</td>
</tr>
<tr>
<th>Date:</th>
<td>{{.Message.Envelope.Date | formatdate}}</td>
</tr>
<tr>
<th>To:</th><td>{{template "addr-list" .Message.Envelope.To}}</td>
</tr>
{{if .Message.Envelope.Cc}}
<tr>
<th>Cc:</th><td>{{template "addr-list" .Message.Envelope.Cc}}</td>
</tr>
{{end}}
{{if .Message.Envelope.Bcc}}
<tr>
<th>Bcc:</th>
<td>{{template "addr-list" .Message.Envelope.Bcc}}</td>
</tr>
{{ end }}
</table>
{{define "addr-list"}}
{{range $i, $addr := .}}
{{if $i}},{{end}}
<strong>{{.PersonalName}}</strong>
&lt;<a href="/compose?to={{.Address}}">{{.Address}}</a>&gt;
{{end}}
{{end}}
{{if .View}}
{{.View}}
{{else}}
<p>Can't preview this message part.</p>
<a href="{{.Message.Uid}}/raw?part={{.Part.PathString}}">Download</a>
{{end}}
{{template "foot.html"}}

View File

@ -0,0 +1,45 @@
<tr>
<th width="1%" class="message-list-checkbox">
<input type="checkbox" id="action-checkbox-all"/>
</th>
<th colspan="3">
<div class="actions-wrap">
<div class="actions-message">
{{ if and (eq .Mailbox.Name "INBOX") (not (eq .Mailbox.Name "Archive")) }}
<button form="messages-form" formaction="/message/{{.Mailbox.Name | pathescape}}/move?to=Archive">Archive</button>
&nbsp;&nbsp;
{{ end }}
{{ if or (eq .Mailbox.Name "INBOX") (eq .Mailbox.Name "Trash") }}
<button form="messages-form" formaction="/message/{{.Mailbox.Name | pathescape}}/move?to=Junk">Report Spam</button>
{{ end }}
{{ if ne .Mailbox.Name "Trash"}}
<button form="messages-form" formaction="/message/{{.Mailbox.Name | pathescape}}/move?to=Trash">Delete</button>
{{ else }}
<button form="messages-form" formaction="/message/{{.Mailbox.Name | pathescape}}/delete">Delete Permanently</button>
{{ end }}
&nbsp;&nbsp;
<a href="{{ .GlobalData.Path }}" class="button-link">Refresh</a>
</div>
<form action="" method="post" class="actions-search">
<input type="text" name="query" value="{{.Query}}" placeholder="Search messages...">
<button>Search</button>
</form>
{{if or (ge .PrevPage 0) (ge .NextPage 0) }}
<div class="actions-pagination">
{{if ge .PrevPage 0}}
{{if ge .PrevPage 1}}<a href="?page=0" class="button-link">«</a>{{ end }}
<a href="?page={{.PrevPage}}" class="button-link"></a>
{{end}}
{{if ge .NextPage 0}}
<a href="?page={{.NextPage}}" class="button-link"></a>
{{end}}
</div>
{{ end }}
</div>
</th>
</tr>

12
themes/alps/nav.html Normal file
View File

@ -0,0 +1,12 @@
<header>
<nav>
<a href="/" class="active">Email</a>
<a href="/calendar">Calendar</a>
<a href="/contacts">Contacts</a>
<div>
<span>{{ .GlobalData.Username }}</span>
<a href="/settings">Settings</a>
<a href="/logout">Sign Out</a>
</div>
</nav>
</header>