diff --git a/booklist/authorlist.go b/booklist/authorlist.go index 748b08b5..5c6ab8d5 100644 --- a/booklist/authorlist.go +++ b/booklist/authorlist.go @@ -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] +} \ No newline at end of file diff --git a/booklist/serieslist.go b/booklist/serieslist.go index a3b55aa8..b18024c6 100644 --- a/booklist/serieslist.go +++ b/booklist/serieslist.go @@ -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] +} \ No newline at end of file diff --git a/public/static/style.css b/public/static/style.css index 330b6a08..3b64b1f7 100644 --- a/public/static/style.css +++ b/public/static/style.css @@ -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"; } \ No newline at end of file diff --git a/public/templates/authors.tmpl b/public/templates/authors.tmpl index 23ae039a..c82a7796 100644 --- a/public/templates/authors.tmpl +++ b/public/templates/authors.tmpl @@ -2,4 +2,8 @@ {{range .Authors}} {{.Name}} {{end}} - \ No newline at end of file + + +{{if .Authors}} +{{template "pagination" .Pagination}} +{{end}} diff --git a/public/templates/books.tmpl b/public/templates/books.tmpl index 7b2dd26c..0650ba9f 100644 --- a/public/templates/books.tmpl +++ b/public/templates/books.tmpl @@ -24,4 +24,8 @@ {{end}} -{{if not .Books}} Not found (or still indexing){{end}} \ No newline at end of file +{{if .Books}} +{{template "pagination" .Pagination}} +{{else}} +Not found (or still indexing) +{{end}} \ No newline at end of file diff --git a/public/templates/pagination.tmpl b/public/templates/pagination.tmpl new file mode 100644 index 00000000..53e525b4 --- /dev/null +++ b/public/templates/pagination.tmpl @@ -0,0 +1,3 @@ + diff --git a/public/templates/search.tmpl b/public/templates/search.tmpl index 4b9c4779..c8ccf3c6 100644 --- a/public/templates/search.tmpl +++ b/public/templates/search.tmpl @@ -1,7 +1,7 @@
{{if .Query}} {{if .Books}} -
{{len .Books}} books found
+
{{.Pagination.ItemTotal}} books found
{{else}}
No books were found matching your query "{{.Query}}"
{{end}} diff --git a/public/templates/seriess.tmpl b/public/templates/seriess.tmpl index 743d16e2..fd6e5abb 100644 --- a/public/templates/seriess.tmpl +++ b/public/templates/seriess.tmpl @@ -2,4 +2,8 @@ {{range .Series}} {{.Name}} {{end}} - \ No newline at end of file + + +{{if .Series}} +{{template "pagination" .Pagination}} +{{end}} diff --git a/server/pagination.go b/server/pagination.go new file mode 100644 index 00000000..b8f907c5 --- /dev/null +++ b/server/pagination.go @@ -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