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 label span { display: inline-block; font-weight: bold; min-width: 100px; }
main.compose form input { width: 80%; } main.compose form input { width: 80%; }
main.compose form textarea { flex: 1 auto; resize: none; margin-top: 1rem; } 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 { border-collapse: collapse; width: 100%; border: 1px solid #eee; }
main table td { white-space: nowrap; padding: 0.3rem; color: #757373; main table td { white-space: nowrap; padding: 0.3rem; color: #757373;
overflow: hidden; overflow: hidden;
@ -159,29 +157,49 @@ main.message table { background-color: white; }
main.message th { width: 5%;} main.message th { width: 5%;}
main.message h1 { font-size: 1.2rem; padding: 0.5rem;} 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-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-sender,
.message-list-unread .message-list-subject, .message-list-unread.message-list-date { color: black;}
.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 { .message-list-date {
text-align: right; 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 { .message-list-unread {
background-color: white; background-color: white;
opacity: 1;
}
.message-list-unread:nth-child(4n+1) {
border-left: 1px solid #f2f2f2; border-left: 1px solid #f2f2f2;
}
.message-list-unread:nth-child(4n+4) {
border-right: 1px solid #f2f2f2; border-right: 1px solid #f2f2f2;
} }
.message-list-unread td { opacity: 1; }
aside .compose-mail { aside .compose-mail {
color: #008d47; color: #008d47;
@ -204,23 +222,89 @@ main table tfoot {
background-color: white; background-color: white;
} }
th form { display: inline; } .message-list {
th input[type="text"] { 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; flex: 1;
margin: 0; margin: 0;
} }
th input[type="text"] + button {
.actions input[type="text"] + button {
margin-left: -4rem; margin-left: -4rem;
width: 4rem; width: 4rem;
border-top-left-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
} }
.actions-wrap { display: flex; flex-direction: row; } .message-list .actions-wrap {
.actions-message { margin-right: 1rem; } flex-grow: 1;
.actions-pagination { margin-left: 1rem; display: flex; flex-direction: row; } }
.actions-search { flex: 1 auto; display: flex; flex-direction: row; }
.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"], input[type="submit"],
.button, .button,
@ -238,6 +322,7 @@ button,
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 3px; border-radius: 3px;
vertical-align: middle; vertical-align: middle;
color: #000;
} }
.button-link { .button-link {
border: 1px solid transparent; border: 1px solid transparent;
@ -249,21 +334,23 @@ button,
input[type="submit"], input[type="submit"],
.button, .button,
button { button,
.button-link {
background-color: #f5f5f5; background-color: #f5f5f5;
border: 1px solid #ddd; border: 1px solid #ddd;
color: black; color: black;
} }
.button:hover, .button:hover,
button:hover { button:hover,
.button-link:hover {
background-color: white; background-color: white;
text-decoration: none;
} }
.button:active, .button:active,
button:active { button:active,
.button-link:active {
color: #ccc; color: #ccc;
background-color: #f8f8f8; background-color: #f8f8f8;
} }
.message-list-checkbox { display: none; }

View file

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

View file

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

View file

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

View file

@ -1,45 +1,47 @@
<tr> <div class="message-list-checkbox">
<th width="1%" class="message-list-checkbox"> <input type="checkbox" id="action-checkbox-all"/>
<input type="checkbox" id="action-checkbox-all"/> </div>
</th> <div class="actions-wrap">
<th colspan="3"> <div class="actions-message">
<div class="actions-wrap"> <div class="action-group">
<div class="actions-message"> {{ if and (eq .Mailbox.Name "INBOX") (not (eq .Mailbox.Name "Archive")) }}
{{ 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>
<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>
{{ end }} {{ end }}
</div> </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>