alps theme: overhaul tables and action lists

This patch contains a whole lot of layout improvements for the alps
theme, mainly replacing the table soup with flexbox and CSS grids, and
fixing up a number of loose ends. This gives us a lot more flexibility
over how the page is laid out. I also cleaned up a lot of other
low-hanging fruit in the layout & styles.
This commit is contained in:
Drew DeVault 2020-05-13 12:29:39 -04:00 committed by Simon Ser
parent aab1f866f6
commit e39879ec9a
No known key found for this signature in database
GPG key ID: 0FDE7BE0E88F5E48
5 changed files with 316 additions and 235 deletions

View file

@ -140,8 +140,6 @@ 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;
@ -159,29 +157,49 @@ main.message table { background-color: white; }
main.message th { width: 5%;}
main.message h1 { font-size: 1.2rem; padding: 0.5rem;}
main.compose .actions {
display: flex;
flex-direction: row;
align-items: center;
}
main.compose .actions button,
main.compose .actions .button-link {
padding: 0.4rem 1rem 0.35rem;
font-weight: bold;
}
main.compose .actions > *:not(:last-child) {
margin-right: 1rem;
}
.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-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-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.message-list-subject a { color: #00c; }
.message-list-unread {
background-color: white;
opacity: 1;
}
.message-list-unread:nth-child(4n+1) {
border-left: 1px solid #f2f2f2;
}
.message-list-unread:nth-child(4n+4) {
border-right: 1px solid #f2f2f2;
}
.message-list-unread td { opacity: 1; }
aside .compose-mail {
color: #008d47;
@ -204,23 +222,89 @@ main table tfoot {
background-color: white;
}
th form { display: inline; }
th input[type="text"] {
.message-list {
display: flex;
}
.message-list section {
width: 100%;
}
.actions {
display: flex;
flex-direction: row;
background-color: white;
padding: 0.3rem;
}
.message-list .actions:first-child {
border-bottom: 1px solid #e0e0e0;
}
.message-list .actions:last-child {
border-top: 1px solid #e0e0e0;
}
.actions input[type="text"] {
flex: 1;
margin: 0;
}
th input[type="text"] + button {
.actions 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; }
.message-list .actions-wrap {
flex-grow: 1;
}
.actions-wrap { display: flex; flex-direction: row; }
.actions-pagination { margin-left: 1rem; display: flex; flex-direction: row; }
.actions-search { display: flex; flex-direction: row; flex-grow: 1; }
.actions-wrap .action-group {
margin-left: 0.3rem;
}
.action-group.grow {
flex-grow: 1;
}
.actions-message {
display: flex;
flex-direction: row;
flex-grow: 1;
align-items: center;
}
.message-list-checkbox {
display: none;
align-self: center;
}
.message-list .messages {
flex-grow: 1;
}
.message-grid {
display: grid;
grid-template-columns: auto 1fr 10fr auto;
grid-template-rows: auto;
}
.message-list .messages .message-grid > * {
white-space: nowrap;
padding: 0.3rem;
overflow: hidden;
text-overflow: ellipsis;
}
.followups a:not(:first-child) {
margin-left: 0.3rem;
}
input[type="submit"],
.button,
@ -238,6 +322,7 @@ button,
border: 1px solid #ddd;
border-radius: 3px;
vertical-align: middle;
color: #000;
}
.button-link {
border: 1px solid transparent;
@ -249,21 +334,23 @@ button,
input[type="submit"],
.button,
button {
button,
.button-link {
background-color: #f5f5f5;
border: 1px solid #ddd;
color: black;
}
.button:hover,
button:hover {
button:hover,
.button-link:hover {
background-color: white;
text-decoration: none;
}
.button:active,
button:active {
button:active,
.button-link:active {
color: #ccc;
background-color: #f8f8f8;
}
.message-list-checkbox { display: none; }

View file

@ -29,10 +29,9 @@
<textarea name="text" class="body">{{.Message.Text}}</textarea>
<div class="actions">
<a href="/mailbox/INBOX">Cancel</a>
&nbsp;&nbsp;
<button type="submit" name="save_as_draft">Save as draft</button>
<button type="submit">Send Message</button>
<button type="submit" name="save_as_draft">Save as draft</button>
<a class="button-link" href="/mailbox/INBOX">Cancel</a>
</div>
</form>

View file

@ -23,43 +23,43 @@
<div class="container">
<form id="messages-form" method="post"></form>
<main>
<table>
<thead>
{{ template "messages-header.html" . }}
</thead>
<tbody>
<main class="message-list">
<section class="actions">
{{ template "messages-header.html" . }}
</section>
<section class="messages">
<div class="message-grid">
{{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="{{.URL}}?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>
<div class="message-list-checkbox message-list-item {{ if not (.HasFlag "\\Seen") }}message-list-unread{{ end }}">
<input type="checkbox" name="uids" value="{{.Uid}}" form="messages-form">
</div>
<div class="message-list-sender message-list-item {{ if not (.HasFlag "\\Seen") }}message-list-unread{{ end }}">
{{ range .Envelope.From }}
{{ if .PersonalName }}
{{.PersonalName}}
{{ else }}
{{.MailboxName}}@{{.HostName}}
{{ end }}
{{ end }}
</tbody>
<tfoot>
{{ template "messages-header.html" .}}
</tfoot>
</table>
</div>
<div class="message-list-subject message-list-item {{ if not (.HasFlag "\\Seen") }}message-list-unread{{ end }}">
<a href="{{.URL}}?part={{.TextPartName}}">
{{if .Envelope.Subject}}
{{.Envelope.Subject}}
{{else}}
(No subject)
{{end}}
</a>
</div>
<div class="message-list-date message-list-item {{ if not (.HasFlag "\\Seen") }}message-list-unread{{ end }}">
{{ .Envelope.Date | formatdate }}
</div>
{{ end }}
</div>
</section>
<section class="actions">
{{ template "messages-header.html" . }}
</section>
</main>
</div>
</div>

View file

@ -24,148 +24,141 @@
<div class="container">
<main class="message">
<section class="actions">
<div class="actions-wrap">
<div class="actions-message">
{{$back := printf "%v?page=%v" .Mailbox.URL .MailboxPage}}
<a href="{{$back}}" class="button-link">« Back</a>
{{ if and (ne .Mailbox.Name "Archive") (ne .Mailbox.Name "Drafts") (ne .Mailbox.Name "Sent") }}
<form class="action-group" method="post" action="/message/{{.Mailbox.Name | pathescape}}/move">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<input type="hidden" name="to" value="Archive">
<input type="hidden" name="next" value="{{$back}}">
<button>Archive</button>
</form>
{{ end }}
<table>
<tr>
<tr>
<th colspan="2">
<div class="actions-wrap">
<div class="actions-message">
{{$back := printf "%v?page=%v" .Mailbox.URL .MailboxPage}}
<a href="{{$back}}" class="button-link">« Back</a>
&nbsp;&nbsp;
{{ if and (ne .Mailbox.Name "INBOX") (ne .Mailbox.Name "Sent") (ne .Mailbox.Name "Drafts") }}
<form class="action-group" 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 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">
<input type="hidden" name="next" value="{{$back}}">
<button>Archive</button>
</form>
{{ end }}
{{ if or (eq .Mailbox.Name "INBOX") (eq .Mailbox.Name "Trash") }}
<form class="action-group" method="post" action="/message/{{.Mailbox.Name | pathescape}}/move">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<input type="hidden" name="next" value="{{$back}}">
<input type="hidden" name="to" value="Junk">
<button>Report Spam</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 "Trash") (eq .Mailbox.Name "Junk") }}
<form class="action-group" method="post" action="/message/{{.Mailbox.Name | pathescape}}/delete">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<input type="hidden" name="next" value="{{$back}}">
<button>Delete Permanently</button>
</form>
{{ else }}
<form class="action-group" method="post" action="/message/{{.Mailbox.Name | pathescape}}/move">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<input type="hidden" name="next" value="{{$back}}">
<input type="hidden" name="to" value="Trash">
<button>Delete</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}}">
<input type="hidden" name="next" value="{{$back}}">
&nbsp;&nbsp;
<input type="hidden" name="to" value="Junk">
<button>Report Spam</button>
</form>
{{ end }}
<form class="action-group" method="post" action="/message/{{.Mailbox.Name | pathescape}}/flag">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<input type="hidden" name="action" value="remove">
<input type="hidden" name="flags" value="\Seen">
<input type="hidden" name="next" value="{{$back}}">
<button>Mark&nbsp;Unread</button>
</form>
{{ 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}}">
<input type="hidden" name="next" value="{{$back}}">
<button>Delete Permanently</button>
</form>
{{ else }}
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/move">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<input type="hidden" name="next" value="{{$back}}">
&nbsp;&nbsp;
<input type="hidden" name="to" value="Trash">
<button>Delete</button>
</form>
{{ end }}
<form class="action-group" method="post" action="/message/{{.Mailbox.Name | pathescape}}/move">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<select class="action-group" name="to">
{{range .Mailboxes}}
<option value="{{.Name}}" {{if eq .Name $.Mailbox.Name}}selected>Move to...{{else}}>{{.Name}}{{ end }}</option>
{{end}}
</select>
<button class="action-group" type="submit">Move</button>
</form>
<form method="post" action="/message/{{.Mailbox.Name | pathescape}}/flag">
<input type="hidden" name="uids" value="{{.Message.Uid}}">
<input type="hidden" name="action" value="remove">
<input type="hidden" name="flags" value="\Seen">
<input type="hidden" name="next" value="{{$back}}">
<button>Mark&nbsp;Unread</button>
</form>
<span class="followups">
{{if .Message.HasFlag "\\Draft"}}
<a class="action-group button-link" href="{{.Message.URL}}/edit?part={{.Part.PathString}}">Edit draft</a>
{{else}}
<a class="action-group button-link" href="{{.Message.URL}}/reply?part={{.Part.PathString}}">Reply</a>
<a class="action-group button-link" href="{{.Message.URL}}/forward?part={{.Part.PathString}}">Forward</a>
{{end}}
</span>
</div>
</div>
</section>
<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;
{{if .Message.HasFlag "\\Draft"}}
<a href="{{.Message.URL}}/edit?part={{.Part.PathString}}">Edit draft</a>
{{else}}
<a href="{{.Message.URL}}/reply?part={{.Part.PathString}}">Reply</a>
&nbsp;&nbsp;
<a href="{{.Message.URL}}/forward?part={{.Part.PathString}}">Forward</a>
<table>
<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}}
</div>
</div>
</th>
</tr>
{{if .Message.Envelope.Bcc}}
<tr>
<th>Bcc:</th>
<td>{{template "addr-list" .Message.Envelope.Bcc}}</td>
</tr>
{{ end }}
</table>
</tr>
<tr>
<th colspan="2">
<h1>
{{if .Message.Envelope.Subject}}
{{.Message.Envelope.Subject}}
{{else}}
(No subject)
{{define "addr-list"}}
{{range $i, $addr := .}}
{{if $i}},{{end}}
<strong>{{.PersonalName}}</strong>
&lt;<a href="/compose?to={{.Address}}">{{.Address}}</a>&gt;
{{end}}
</h1>
</th>
</tr>
{{end}}
<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}}
{{if .View}}
{{.View}}
{{else}}
<p>Can't preview this message part.</p>
<a href="{{.Message.Uid}}/raw?part={{.Part.PathString}}">Download</a>
{{end}}
</main>
</div>
</div>
{{template "foot.html"}}

View file

@ -1,45 +1,47 @@
<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.URL.String }}" class="button-link">Refresh</a>
</div>
<form action="" method="get" 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>
<div class="message-list-checkbox">
<input type="checkbox" id="action-checkbox-all"/>
</div>
<div class="actions-wrap">
<div class="actions-message">
<div class="action-group">
{{ 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>
{{ end }}
</div>
</th>
</tr>
<div class="action-group">
{{ 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 }}
</div>
<div class="action-group">
{{ 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 }}
</div>
<div class="action-group">
<a href="{{ .GlobalData.URL.String }}" class="button-link">Refresh</a>
</div>
</div>
<form method="get" 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>