Skip to content
This repository was archived by the owner on Jun 27, 2020. It is now read-only.

Implement pagination support #83

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions booklist/authorlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,17 @@ func (al AuthorList) Sorted(less func(a, b struct{ Name, ID string }) bool) Auth
})
return nal
}

func (al AuthorList) Skip(n int) AuthorList {
if n >= len(al) {
return AuthorList{}
}
return al[n:]
}

func (al AuthorList) Take(n int) AuthorList {
if n > len(al) {
return al
}
return al[:n]
}
14 changes: 14 additions & 0 deletions booklist/serieslist.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,17 @@ func (sl SeriesList) Sorted(less func(a, b struct{ Name, ID string }) bool) Seri
})
return nsl
}

func (sl SeriesList) Skip(n int) SeriesList {
if n >= len(sl) {
return SeriesList{}
}
return sl[n:]
}

func (sl SeriesList) Take(n int) SeriesList {
if n > len(sl) {
return sl
}
return sl[:n]
}
33 changes: 33 additions & 0 deletions public/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -626,4 +626,37 @@ a:visited {

a:hover {
color: #004479;
}

.pagination,
.pagination a {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
margin-bottom: 10px;
}

.pagination a,
.pagination a:link,
.pagination a:visited,
.pagination a:active,
.pagination a:hover {
text-decoration: none;
padding: 4px 12px;
border: none;
background: #ddd;
color: #000;
margin-right: 2px;
}

.pagination a.current {
font-weight: bold;
}
.pagination a.next:before {
content: "\00bb";
}
.pagination a.prev:before {
content: "\00ab";
}
6 changes: 5 additions & 1 deletion public/templates/authors.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@
{{range .Authors}}
<a href="/authors/{{.ID}}" class="item">{{.Name}}</a>
{{end}}
</div>
</div>

{{if .Authors}}
{{template "pagination" .Pagination}}
{{end}}
6 changes: 5 additions & 1 deletion public/templates/books.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@
{{end}}
</div>

{{if not .Books}} Not found (or still indexing){{end}}
{{if .Books}}
{{template "pagination" .Pagination}}
{{else}}
Not found (or still indexing)
{{end}}
3 changes: 3 additions & 0 deletions public/templates/pagination.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<div class="pagination">
{{range .Pages}}<a href="?{{.QueryString}}"{{if .Current}} class="current"{{end}}{{if .Next}} class="next"{{end}}{{if .Prev}} class="prev"{{end}}>{{if .Index}}{{.Index}}{{end}}</a>{{end}}
</div>
2 changes: 1 addition & 1 deletion public/templates/search.tmpl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<br>
{{if .Query}}
{{if .Books}}
<center>{{len .Books}} books found</center>
<center>{{.Pagination.ItemTotal}} books found</center>
{{else}}
<center>No books were found matching your query "{{.Query}}"</center>
{{end}}
Expand Down
6 changes: 5 additions & 1 deletion public/templates/seriess.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@
{{range .Series}}
<a href="/series/{{.ID}}" class="item">{{.Name}}</a>
{{end}}
</div>
</div>

{{if .Series}}
{{template "pagination" .Pagination}}
{{end}}
126 changes: 126 additions & 0 deletions server/pagination.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package server

import (
"net/url"
"strconv"
"strings"
"fmt"
"html/template"
)

type Pagination struct {
ItemOffset int
ItemLimit int
ItemTotal int
CurrentPage int
TotalPages int

queryStringFormat string
}

type Page struct {
Index int
Current bool
Offset int
Limit int
QueryString template.URL

Prev bool
Next bool
}

const defaultQueryLimit = 24 // default number of items to return, if no limit is specified; 24 is evenly divisible by the default of 4 items displayed per row
const maxQueryLimit = 1000 // maximum number of items to return, to prevent users from doing dumb things

func queryStringFormat(o url.Values) string {
n := url.Values{}
for k, v := range o {
if k != "offset" && k != "limit" {
for _, vv := range v {
n.Set(k, vv)
}
}
}
f := strings.Replace(n.Encode(),"%","%%", -1)
if len(f) != 0 {
f += "&"
}
f += "offset=%d&limit=%d"
return f
}

func NewPagination(v url.Values, totalItems int) *Pagination {

p := &Pagination{
queryStringFormat: queryStringFormat(v),
ItemTotal: totalItems,
}

p.ItemOffset, _ = strconv.Atoi(v.Get("offset"))
if p.ItemOffset < 0 {
p.ItemOffset = 0
}

p.ItemLimit, _ = strconv.Atoi(v.Get("limit"))
if p.ItemLimit < 1 {
p.ItemLimit = defaultQueryLimit
}
if p.ItemLimit > maxQueryLimit {
p.ItemLimit = maxQueryLimit
}

p.TotalPages = totalItems / p.ItemLimit
if totalItems % p.ItemLimit != 0 {
p.TotalPages++
}

p.CurrentPage = (p.ItemOffset+1) / p.ItemLimit
if (p.ItemOffset+1) % p.ItemLimit != 0 {
p.CurrentPage++
}

return p
}

func (p *Pagination) Pages() []Page {
pages := make([]Page,0,p.TotalPages)

if p.CurrentPage != 1 {
offset := p.ItemOffset - p.ItemLimit
if offset < 0 {
offset = 0
}
pages = append(pages,Page{
Prev: true,
Current: false,
Offset: offset,
Limit: p.ItemLimit,
QueryString: template.URL(fmt.Sprintf(p.queryStringFormat,offset,p.ItemLimit)),
})
}

for idx := 0; idx<p.TotalPages; idx++ {
pages = append(pages,Page{
Index: idx+1,
Current: idx+1 == p.CurrentPage,
Offset: idx*p.ItemLimit,
Limit: p.ItemLimit,
QueryString: template.URL(fmt.Sprintf(p.queryStringFormat,idx*p.ItemLimit,p.ItemLimit)),
})
}

if p.CurrentPage != p.TotalPages {
offset := p.ItemOffset + p.ItemLimit
pages = append(pages,Page{
Next: true,
Current: false,
Offset: offset,
Limit: p.ItemLimit,
QueryString: template.URL(fmt.Sprintf(p.queryStringFormat,offset,p.ItemLimit)),
})
}



return pages
}
59 changes: 48 additions & 11 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,16 +298,23 @@ func (s *Server) handleDownload(w http.ResponseWriter, r *http.Request, p httpro
}

func (s *Server) handleAuthors(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {

al := s.Indexer.BookList().Authors().Sorted(func(a, b struct{ Name, ID string }) bool {
return a.Name < b.Name
})

pagination := NewPagination(r.URL.Query(),len(al))
al = al.Skip(pagination.ItemOffset).Take(pagination.ItemLimit)

s.render.HTML(w, http.StatusOK, "authors", map[string]interface{}{
"CurVersion": s.version,
"PageTitle": "Authors",
"ShowBar": true,
"ShowSearch": false,
"ShowViewSelector": true,
"Title": "Authors",
"Authors": s.Indexer.BookList().Authors().Sorted(func(a, b struct{ Name, ID string }) bool {
return a.Name < b.Name
}),
"Authors": al,
"Pagination": pagination,
})
}

Expand All @@ -326,6 +333,9 @@ func (s *Server) handleAuthor(w http.ResponseWriter, r *http.Request, p httprout
bl, _ = bl.SortBy("title-asc")
bl, _ = bl.SortBy(r.URL.Query().Get("sort"))

pagination := NewPagination(r.URL.Query(),len(bl))
bl = bl.Skip(pagination.ItemOffset).Take(pagination.ItemLimit)

s.render.HTML(w, http.StatusOK, "author", map[string]interface{}{
"CurVersion": s.version,
"PageTitle": aname,
Expand All @@ -334,6 +344,7 @@ func (s *Server) handleAuthor(w http.ResponseWriter, r *http.Request, p httprout
"ShowViewSelector": true,
"Title": aname,
"Books": bl,
"Pagination": pagination,
})
return
}
Expand All @@ -350,16 +361,23 @@ func (s *Server) handleAuthor(w http.ResponseWriter, r *http.Request, p httprout
}

func (s *Server) handleSeriess(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {

seriess := s.Indexer.BookList().Series().Sorted(func(a, b struct{ Name, ID string }) bool {
return a.Name < b.Name
})

pagination := NewPagination(r.URL.Query(),len(seriess))
seriess = seriess.Skip(pagination.ItemOffset).Take(pagination.ItemLimit)

s.render.HTML(w, http.StatusOK, "seriess", map[string]interface{}{
"CurVersion": s.version,
"PageTitle": "Series",
"ShowBar": true,
"ShowSearch": false,
"ShowViewSelector": true,
"Title": "Series",
"Series": s.Indexer.BookList().Series().Sorted(func(a, b struct{ Name, ID string }) bool {
return a.Name < b.Name
}),
"Series": seriess,
"Pagination": pagination,
})
}

Expand All @@ -372,11 +390,25 @@ func (s *Server) handleSeries(w http.ResponseWriter, r *http.Request, p httprout
}

if sname != "" {
/* the bl variable created here was unused by the original s.render.HTML() call below and seems to be
dead code... @geek1011, safe to remove this?

bl := s.Indexer.BookList().Filtered(func(book *booklist.Book) bool {
return book.Series != "" && book.SeriesID() == p.ByName("id")
})
bl, _ = bl.SortBy("seriesindex-asc")
bl, _ = bl.SortBy(r.URL.Query().Get("sort"))
*/


bl := s.Indexer.BookList().Filtered(func(book *booklist.Book) bool {
return book.Series != "" && book.SeriesID() == p.ByName("id")
}).Sorted(func(a, b *booklist.Book) bool {
return a.SeriesIndex < b.SeriesIndex
})

pagination := NewPagination(r.URL.Query(),len(bl))
bl = bl.Skip(pagination.ItemOffset).Take(pagination.ItemLimit)

s.render.HTML(w, http.StatusOK, "series", map[string]interface{}{
"CurVersion": s.version,
Expand All @@ -385,11 +417,8 @@ func (s *Server) handleSeries(w http.ResponseWriter, r *http.Request, p httprout
"ShowSearch": false,
"ShowViewSelector": true,
"Title": sname,
"Books": s.Indexer.BookList().Filtered(func(book *booklist.Book) bool {
return book.Series != "" && book.SeriesID() == p.ByName("id")
}).Sorted(func(a, b *booklist.Book) bool {
return a.SeriesIndex < b.SeriesIndex
}),
"Books": bl,
"Pagination": pagination,
})
return
}
Expand All @@ -409,6 +438,9 @@ func (s *Server) handleBooks(w http.ResponseWriter, r *http.Request, _ httproute
bl, _ := s.Indexer.BookList().SortBy("modified-desc")
bl, _ = bl.SortBy(r.URL.Query().Get("sort"))

pagination := NewPagination(r.URL.Query(),len(bl))
bl = bl.Skip(pagination.ItemOffset).Take(pagination.ItemLimit)

s.render.HTML(w, http.StatusOK, "books", map[string]interface{}{
"CurVersion": s.version,
"PageTitle": "Books",
Expand All @@ -417,6 +449,7 @@ func (s *Server) handleBooks(w http.ResponseWriter, r *http.Request, _ httproute
"ShowViewSelector": true,
"Title": "",
"Books": bl,
"Pagination": pagination,
})
}

Expand Down Expand Up @@ -462,6 +495,9 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request, _ httprout
bl, _ = bl.SortBy("title-asc")
bl, _ = bl.SortBy(r.URL.Query().Get("sort"))

pagination := NewPagination(r.URL.Query(),len(bl))
bl = bl.Skip(pagination.ItemOffset).Take(pagination.ItemLimit)

s.render.HTML(w, http.StatusOK, "search", map[string]interface{}{
"CurVersion": s.version,
"PageTitle": "Search Results",
Expand All @@ -471,6 +507,7 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request, _ httprout
"Title": "Search Results",
"Query": q,
"Books": bl,
"Pagination": pagination,
})
return
}
Expand Down