Add basic pagination to message list
References: https://todo.sr.ht/~sircmpwn/koushin/22
This commit is contained in:
parent
6344806755
commit
8de93c50d2
7 changed files with 61 additions and 21 deletions
|
@ -21,7 +21,7 @@ func generateToken() (string, error) {
|
||||||
var ErrSessionExpired = errors.New("session expired")
|
var ErrSessionExpired = errors.New("session expired")
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
locker sync.Mutex
|
locker sync.Mutex
|
||||||
imapConn *imapclient.Client
|
imapConn *imapclient.Client
|
||||||
username, password string
|
username, password string
|
||||||
}
|
}
|
||||||
|
|
22
imap.go
22
imap.go
|
@ -206,23 +206,23 @@ func (msg *imapMessage) PartTree() *IMAPPartNode {
|
||||||
return imapPartTree(msg.BodyStructure, nil)
|
return imapPartTree(msg.BodyStructure, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func listMessages(conn *imapclient.Client, mboxName string) ([]imapMessage, error) {
|
func listMessages(conn *imapclient.Client, mboxName string, page int) ([]imapMessage, error) {
|
||||||
if err := ensureMailboxSelected(conn, mboxName); err != nil {
|
if err := ensureMailboxSelected(conn, mboxName); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
n := uint32(50)
|
|
||||||
|
|
||||||
mbox := conn.Mailbox()
|
mbox := conn.Mailbox()
|
||||||
from := uint32(1)
|
to := int(mbox.Messages) - page*messagesPerPage
|
||||||
to := mbox.Messages
|
from := to - messagesPerPage
|
||||||
if mbox.Messages == 0 {
|
if from <= 0 {
|
||||||
return nil, nil
|
from = 1
|
||||||
} else if mbox.Messages > n {
|
|
||||||
from = mbox.Messages - n
|
|
||||||
}
|
}
|
||||||
|
if to <= 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
seqSet := new(imap.SeqSet)
|
seqSet := new(imap.SeqSet)
|
||||||
seqSet.AddRange(from, to)
|
seqSet.AddRange(uint32(from), uint32(to))
|
||||||
|
|
||||||
fetch := []imap.FetchItem{imap.FetchEnvelope, imap.FetchUid, imap.FetchBodyStructure}
|
fetch := []imap.FetchItem{imap.FetchEnvelope, imap.FetchUid, imap.FetchBodyStructure}
|
||||||
|
|
||||||
|
@ -232,7 +232,7 @@ func listMessages(conn *imapclient.Client, mboxName string) ([]imapMessage, erro
|
||||||
done <- conn.Fetch(seqSet, fetch, ch)
|
done <- conn.Fetch(seqSet, fetch, ch)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
msgs := make([]imapMessage, 0, n)
|
msgs := make([]imapMessage, 0, to-from)
|
||||||
for msg := range ch {
|
for msg := range ch {
|
||||||
msgs = append(msgs, imapMessage{msg})
|
msgs = append(msgs, imapMessage{msg})
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,10 +20,23 @@
|
||||||
<ul>
|
<ul>
|
||||||
{{range .Messages}}
|
{{range .Messages}}
|
||||||
<li><a href="/message/{{$.Mailbox.Name | pathescape}}/{{.Uid}}?part={{.TextPartName}}">
|
<li><a href="/message/{{$.Mailbox.Name | pathescape}}/{{.Uid}}?part={{.TextPartName}}">
|
||||||
{{.Envelope.Subject}}
|
{{if .Envelope.Subject}}
|
||||||
|
{{.Envelope.Subject}}
|
||||||
|
{{else}}
|
||||||
|
(No subject)
|
||||||
|
{{end}}
|
||||||
</a></li>
|
</a></li>
|
||||||
{{end}}
|
{{end}}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{{if ge .PrevPage 0}}
|
||||||
|
<a href="?page={{.PrevPage}}">Prev</a>
|
||||||
|
{{end}}
|
||||||
|
{{if ge .NextPage 0}}
|
||||||
|
<a href="?page={{.NextPage}}">Next</a>
|
||||||
|
{{end}}
|
||||||
|
</p>
|
||||||
{{else}}
|
{{else}}
|
||||||
<p>Mailbox is empty.</p>
|
<p>Mailbox is empty.</p>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -6,7 +6,13 @@
|
||||||
<a href="/mailbox/{{.Mailbox.Name | pathescape}}">Back</a>
|
<a href="/mailbox/{{.Mailbox.Name | pathescape}}">Back</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>{{.Message.Envelope.Subject}}</h2>
|
<h2>
|
||||||
|
{{if .Message.Envelope.Subject}}
|
||||||
|
{{.Message.Envelope.Subject}}
|
||||||
|
{{else}}
|
||||||
|
(No subject)
|
||||||
|
{{end}}
|
||||||
|
</h2>
|
||||||
|
|
||||||
{{define "message-part-tree"}}
|
{{define "message-part-tree"}}
|
||||||
{{/* nested templates can't access the parent's context */}}
|
{{/* nested templates can't access the parent's context */}}
|
||||||
|
|
23
server.go
23
server.go
|
@ -6,6 +6,7 @@ import (
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -18,6 +19,8 @@ import (
|
||||||
|
|
||||||
const cookieName = "koushin_session"
|
const cookieName = "koushin_session"
|
||||||
|
|
||||||
|
const messagesPerPage = 50
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
imap struct {
|
imap struct {
|
||||||
host string
|
host string
|
||||||
|
@ -366,6 +369,14 @@ func New(imapURL, smtpURL string) *echo.Echo {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, err)
|
return echo.NewHTTPError(http.StatusBadRequest, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page := 0
|
||||||
|
if pageStr := ctx.QueryParam("page"); pageStr != "" {
|
||||||
|
var err error
|
||||||
|
if page, err = strconv.Atoi(pageStr); err != nil || page < 0 {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "invalid page index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var mailboxes []*imap.MailboxInfo
|
var mailboxes []*imap.MailboxInfo
|
||||||
var msgs []imapMessage
|
var msgs []imapMessage
|
||||||
var mbox *imap.MailboxStatus
|
var mbox *imap.MailboxStatus
|
||||||
|
@ -374,7 +385,7 @@ func New(imapURL, smtpURL string) *echo.Echo {
|
||||||
if mailboxes, err = listMailboxes(c); err != nil {
|
if mailboxes, err = listMailboxes(c); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if msgs, err = listMessages(c, mboxName); err != nil {
|
if msgs, err = listMessages(c, mboxName, page); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
mbox = c.Mailbox()
|
mbox = c.Mailbox()
|
||||||
|
@ -384,10 +395,20 @@ func New(imapURL, smtpURL string) *echo.Echo {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevPage, nextPage := -1, -1
|
||||||
|
if page > 0 {
|
||||||
|
prevPage = page - 1
|
||||||
|
}
|
||||||
|
if (page+1)*messagesPerPage < int(mbox.Messages) {
|
||||||
|
nextPage = page + 1
|
||||||
|
}
|
||||||
|
|
||||||
return ctx.Render(http.StatusOK, "mailbox.html", map[string]interface{}{
|
return ctx.Render(http.StatusOK, "mailbox.html", map[string]interface{}{
|
||||||
"Mailbox": mbox,
|
"Mailbox": mbox,
|
||||||
"Mailboxes": mailboxes,
|
"Mailboxes": mailboxes,
|
||||||
"Messages": msgs,
|
"Messages": msgs,
|
||||||
|
"PrevPage": prevPage,
|
||||||
|
"NextPage": nextPage,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
10
smtp.go
10
smtp.go
|
@ -3,9 +3,9 @@ package koushin
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/emersion/go-message/mail"
|
"github.com/emersion/go-message/mail"
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
|
@ -50,11 +50,11 @@ func (s *Server) connectSMTP() (*smtp.Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type OutgoingMessage struct {
|
type OutgoingMessage struct {
|
||||||
From string
|
From string
|
||||||
To []string
|
To []string
|
||||||
Subject string
|
Subject string
|
||||||
InReplyTo string
|
InReplyTo string
|
||||||
Text string
|
Text string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (msg *OutgoingMessage) ToString() string {
|
func (msg *OutgoingMessage) ToString() string {
|
||||||
|
|
|
@ -2,9 +2,9 @@ package koushin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"net/url"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseUid(s string) (uint32, error) {
|
func parseUid(s string) (uint32, error) {
|
||||||
|
|
Loading…
Reference in a new issue