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