summaryrefslogtreecommitdiffstats
path: root/modules/paginator
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-10-11 10:27:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-10-11 10:27:00 +0000
commit65aa53fc52ff15efe54df4147564828d535837f8 (patch)
tree31c51dad04fdcca80e6d3043c8bd49d2f1a51f83 /modules/paginator
parentInitial commit. (diff)
downloadforgejo-debian.tar.xz
forgejo-debian.zip
Adding upstream version 8.0.3.HEADupstream/8.0.3upstreamdebian
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/paginator')
-rw-r--r--modules/paginator/paginator.go204
-rw-r--r--modules/paginator/paginator_test.go312
2 files changed, 516 insertions, 0 deletions
diff --git a/modules/paginator/paginator.go b/modules/paginator/paginator.go
new file mode 100644
index 00000000..8258d194
--- /dev/null
+++ b/modules/paginator/paginator.go
@@ -0,0 +1,204 @@
+// Copyright 2022 The Gitea Authors.
+// Copyright 2015 https://github.com/unknwon. Licensed under the Apache License, Version 2.0
+// SPDX-License-Identifier: Apache-2.0
+
+package paginator
+
+/*
+In template:
+
+```html
+{{if not .Page.IsFirst}}[First](1){{end}}
+{{if .Page.HasPrevious}}[Previous]({{.Page.Previous}}){{end}}
+
+{{range .Page.Pages}}
+ {{if eq .Num -1}}
+ ...
+ {{else}}
+ {{.Num}}{{if .IsCurrent}}(current){{end}}
+ {{end}}
+{{end}}
+
+{{if .Page.HasNext}}[Next]({{.Page.Next}}){{end}}
+{{if not .Page.IsLast}}[Last]({{.Page.TotalPages}}){{end}}
+```
+
+Output:
+
+```
+[First](1) [Previous](2) ... 2 3(current) 4 ... [Next](4) [Last](5)
+```
+*/
+
+// Paginator represents a set of results of pagination calculations.
+type Paginator struct {
+ total int // total rows count
+ pagingNum int // how many rows in one page
+ current int // current page number
+ numPages int // how many pages to show on the UI
+}
+
+// New initialize a new pagination calculation and returns a Paginator as result.
+func New(total, pagingNum, current, numPages int) *Paginator {
+ if pagingNum <= 0 {
+ pagingNum = 1
+ }
+ if current <= 0 {
+ current = 1
+ }
+ p := &Paginator{total, pagingNum, current, numPages}
+ if p.current > p.TotalPages() {
+ p.current = p.TotalPages()
+ }
+ return p
+}
+
+// IsFirst returns true if current page is the first page.
+func (p *Paginator) IsFirst() bool {
+ return p.current == 1
+}
+
+// HasPrevious returns true if there is a previous page relative to current page.
+func (p *Paginator) HasPrevious() bool {
+ return p.current > 1
+}
+
+func (p *Paginator) Previous() int {
+ if !p.HasPrevious() {
+ return p.current
+ }
+ return p.current - 1
+}
+
+// HasNext returns true if there is a next page relative to current page.
+func (p *Paginator) HasNext() bool {
+ return p.total > p.current*p.pagingNum
+}
+
+func (p *Paginator) Next() int {
+ if !p.HasNext() {
+ return p.current
+ }
+ return p.current + 1
+}
+
+// IsLast returns true if current page is the last page.
+func (p *Paginator) IsLast() bool {
+ if p.total == 0 {
+ return true
+ }
+ return p.total > (p.current-1)*p.pagingNum && !p.HasNext()
+}
+
+// Total returns number of total rows.
+func (p *Paginator) Total() int {
+ return p.total
+}
+
+// TotalPages returns number of total pages.
+func (p *Paginator) TotalPages() int {
+ if p.total == 0 {
+ return 1
+ }
+ return (p.total + p.pagingNum - 1) / p.pagingNum
+}
+
+// Current returns current page number.
+func (p *Paginator) Current() int {
+ return p.current
+}
+
+// PagingNum returns number of page size.
+func (p *Paginator) PagingNum() int {
+ return p.pagingNum
+}
+
+// Page presents a page in the paginator.
+type Page struct {
+ num int
+ isCurrent bool
+}
+
+func (p *Page) Num() int {
+ return p.num
+}
+
+func (p *Page) IsCurrent() bool {
+ return p.isCurrent
+}
+
+func getMiddleIdx(numPages int) int {
+ return (numPages + 1) / 2
+}
+
+// Pages returns a list of nearby page numbers relative to current page.
+// If value is -1 means "..." that more pages are not showing.
+func (p *Paginator) Pages() []*Page {
+ if p.numPages == 0 {
+ return []*Page{}
+ } else if p.numPages == 1 && p.TotalPages() == 1 {
+ // Only show current page.
+ return []*Page{{1, true}}
+ }
+
+ // Total page number is less or equal.
+ if p.TotalPages() <= p.numPages {
+ pages := make([]*Page, p.TotalPages())
+ for i := range pages {
+ pages[i] = &Page{i + 1, i+1 == p.current}
+ }
+ return pages
+ }
+
+ numPages := p.numPages
+ offsetIdx := 0
+ hasMoreNext := false
+
+ // Check more previous and next pages.
+ previousNum := getMiddleIdx(p.numPages) - 1
+ if previousNum > p.current-1 {
+ previousNum -= previousNum - (p.current - 1)
+ }
+ nextNum := p.numPages - previousNum - 1
+ if p.current+nextNum > p.TotalPages() {
+ delta := nextNum - (p.TotalPages() - p.current)
+ nextNum -= delta
+ previousNum += delta
+ }
+
+ offsetVal := p.current - previousNum
+ if offsetVal > 1 {
+ numPages++
+ offsetIdx = 1
+ }
+
+ if p.current+nextNum < p.TotalPages() {
+ numPages++
+ hasMoreNext = true
+ }
+
+ pages := make([]*Page, numPages)
+
+ // There are more previous pages.
+ if offsetIdx == 1 {
+ pages[0] = &Page{-1, false}
+ }
+ // There are more next pages.
+ if hasMoreNext {
+ pages[len(pages)-1] = &Page{-1, false}
+ }
+
+ // Check previous pages.
+ for i := 0; i < previousNum; i++ {
+ pages[offsetIdx+i] = &Page{i + offsetVal, false}
+ }
+
+ pages[offsetIdx+previousNum] = &Page{p.current, true}
+
+ // Check next pages.
+ for i := 1; i <= nextNum; i++ {
+ pages[offsetIdx+previousNum+i] = &Page{p.current + i, false}
+ }
+
+ return pages
+}
diff --git a/modules/paginator/paginator_test.go b/modules/paginator/paginator_test.go
new file mode 100644
index 00000000..8a56ee51
--- /dev/null
+++ b/modules/paginator/paginator_test.go
@@ -0,0 +1,312 @@
+// Copyright 2022 The Gitea Authors.
+// Copyright 2015 https://github.com/unknwon. Licensed under the Apache License, Version 2.0
+// SPDX-License-Identifier: Apache-2.0
+
+package paginator
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPaginator(t *testing.T) {
+ t.Run("Basic logics", func(t *testing.T) {
+ p := New(0, -1, -1, 0)
+ assert.Equal(t, 1, p.PagingNum())
+ assert.True(t, p.IsFirst())
+ assert.False(t, p.HasPrevious())
+ assert.Equal(t, 1, p.Previous())
+ assert.False(t, p.HasNext())
+ assert.Equal(t, 1, p.Next())
+ assert.True(t, p.IsLast())
+ assert.Equal(t, 0, p.Total())
+
+ p = New(1, 10, 2, 0)
+ assert.Equal(t, 10, p.PagingNum())
+ assert.True(t, p.IsFirst())
+ assert.False(t, p.HasPrevious())
+ assert.False(t, p.HasNext())
+ assert.True(t, p.IsLast())
+
+ p = New(10, 10, 1, 0)
+ assert.Equal(t, 10, p.PagingNum())
+ assert.True(t, p.IsFirst())
+ assert.False(t, p.HasPrevious())
+ assert.False(t, p.HasNext())
+ assert.True(t, p.IsLast())
+
+ p = New(11, 10, 1, 0)
+ assert.Equal(t, 10, p.PagingNum())
+ assert.True(t, p.IsFirst())
+ assert.False(t, p.HasPrevious())
+ assert.True(t, p.HasNext())
+ assert.Equal(t, 2, p.Next())
+ assert.False(t, p.IsLast())
+
+ p = New(11, 10, 2, 0)
+ assert.Equal(t, 10, p.PagingNum())
+ assert.False(t, p.IsFirst())
+ assert.True(t, p.HasPrevious())
+ assert.Equal(t, 1, p.Previous())
+ assert.False(t, p.HasNext())
+ assert.True(t, p.IsLast())
+
+ p = New(20, 10, 2, 0)
+ assert.Equal(t, 10, p.PagingNum())
+ assert.False(t, p.IsFirst())
+ assert.True(t, p.HasPrevious())
+ assert.False(t, p.HasNext())
+ assert.True(t, p.IsLast())
+
+ p = New(25, 10, 2, 0)
+ assert.Equal(t, 10, p.PagingNum())
+ assert.False(t, p.IsFirst())
+ assert.True(t, p.HasPrevious())
+ assert.True(t, p.HasNext())
+ assert.False(t, p.IsLast())
+ })
+
+ t.Run("Generate pages", func(t *testing.T) {
+ p := New(0, 10, 1, 0)
+ pages := p.Pages()
+ assert.Empty(t, pages)
+ })
+
+ t.Run("Only current page", func(t *testing.T) {
+ p := New(0, 10, 1, 1)
+ pages := p.Pages()
+ assert.Len(t, pages, 1)
+ assert.Equal(t, 1, pages[0].Num())
+ assert.True(t, pages[0].IsCurrent())
+
+ p = New(1, 10, 1, 1)
+ pages = p.Pages()
+ assert.Len(t, pages, 1)
+ assert.Equal(t, 1, pages[0].Num())
+ assert.True(t, pages[0].IsCurrent())
+ })
+
+ t.Run("Total page number is less or equal", func(t *testing.T) {
+ p := New(1, 10, 1, 2)
+ pages := p.Pages()
+ assert.Len(t, pages, 1)
+ assert.Equal(t, 1, pages[0].Num())
+ assert.True(t, pages[0].IsCurrent())
+
+ p = New(11, 10, 1, 2)
+ pages = p.Pages()
+ assert.Len(t, pages, 2)
+ assert.Equal(t, 1, pages[0].Num())
+ assert.True(t, pages[0].IsCurrent())
+ assert.Equal(t, 2, pages[1].Num())
+ assert.False(t, pages[1].IsCurrent())
+
+ p = New(11, 10, 2, 2)
+ pages = p.Pages()
+ assert.Len(t, pages, 2)
+ assert.Equal(t, 1, pages[0].Num())
+ assert.False(t, pages[0].IsCurrent())
+ assert.Equal(t, 2, pages[1].Num())
+ assert.True(t, pages[1].IsCurrent())
+
+ p = New(25, 10, 2, 3)
+ pages = p.Pages()
+ assert.Len(t, pages, 3)
+ assert.Equal(t, 1, pages[0].Num())
+ assert.False(t, pages[0].IsCurrent())
+ assert.Equal(t, 2, pages[1].Num())
+ assert.True(t, pages[1].IsCurrent())
+ assert.Equal(t, 3, pages[2].Num())
+ assert.False(t, pages[2].IsCurrent())
+ })
+
+ t.Run("Has more previous pages ", func(t *testing.T) {
+ // ... 2
+ p := New(11, 10, 2, 1)
+ pages := p.Pages()
+ assert.Len(t, pages, 2)
+ assert.Equal(t, -1, pages[0].Num())
+ assert.False(t, pages[0].IsCurrent())
+ assert.Equal(t, 2, pages[1].Num())
+ assert.True(t, pages[1].IsCurrent())
+
+ // ... 2 3
+ p = New(21, 10, 2, 2)
+ pages = p.Pages()
+ assert.Len(t, pages, 3)
+ assert.Equal(t, -1, pages[0].Num())
+ assert.False(t, pages[0].IsCurrent())
+ assert.Equal(t, 2, pages[1].Num())
+ assert.True(t, pages[1].IsCurrent())
+ assert.Equal(t, 3, pages[2].Num())
+ assert.False(t, pages[2].IsCurrent())
+
+ // ... 2 3 4
+ p = New(31, 10, 3, 3)
+ pages = p.Pages()
+ assert.Len(t, pages, 4)
+ assert.Equal(t, -1, pages[0].Num())
+ assert.False(t, pages[0].IsCurrent())
+ assert.Equal(t, 2, pages[1].Num())
+ assert.False(t, pages[1].IsCurrent())
+ assert.Equal(t, 3, pages[2].Num())
+ assert.True(t, pages[2].IsCurrent())
+ assert.Equal(t, 4, pages[3].Num())
+ assert.False(t, pages[3].IsCurrent())
+
+ // ... 3 4 5
+ p = New(41, 10, 4, 3)
+ pages = p.Pages()
+ assert.Len(t, pages, 4)
+ assert.Equal(t, -1, pages[0].Num())
+ assert.False(t, pages[0].IsCurrent())
+ assert.Equal(t, 3, pages[1].Num())
+ assert.False(t, pages[1].IsCurrent())
+ assert.Equal(t, 4, pages[2].Num())
+ assert.True(t, pages[2].IsCurrent())
+ assert.Equal(t, 5, pages[3].Num())
+ assert.False(t, pages[3].IsCurrent())
+
+ // ... 4 5 6 7 8 9 10
+ p = New(100, 10, 9, 7)
+ pages = p.Pages()
+ assert.Len(t, pages, 8)
+ assert.Equal(t, -1, pages[0].Num())
+ assert.False(t, pages[0].IsCurrent())
+ assert.Equal(t, 4, pages[1].Num())
+ assert.False(t, pages[1].IsCurrent())
+ assert.Equal(t, 5, pages[2].Num())
+ assert.False(t, pages[2].IsCurrent())
+ assert.Equal(t, 6, pages[3].Num())
+ assert.False(t, pages[3].IsCurrent())
+ assert.Equal(t, 7, pages[4].Num())
+ assert.False(t, pages[4].IsCurrent())
+ assert.Equal(t, 8, pages[5].Num())
+ assert.False(t, pages[5].IsCurrent())
+ assert.Equal(t, 9, pages[6].Num())
+ assert.True(t, pages[6].IsCurrent())
+ assert.Equal(t, 10, pages[7].Num())
+ assert.False(t, pages[7].IsCurrent())
+ })
+
+ t.Run("Has more next pages", func(t *testing.T) {
+ // 1 ...
+ p := New(21, 10, 1, 1)
+ pages := p.Pages()
+ assert.Len(t, pages, 2)
+ assert.Equal(t, 1, pages[0].Num())
+ assert.True(t, pages[0].IsCurrent())
+ assert.Equal(t, -1, pages[1].Num())
+ assert.False(t, pages[1].IsCurrent())
+
+ // 1 2 ...
+ p = New(21, 10, 1, 2)
+ pages = p.Pages()
+ assert.Len(t, pages, 3)
+ assert.Equal(t, 1, pages[0].Num())
+ assert.True(t, pages[0].IsCurrent())
+ assert.Equal(t, 2, pages[1].Num())
+ assert.False(t, pages[1].IsCurrent())
+ assert.Equal(t, -1, pages[2].Num())
+ assert.False(t, pages[2].IsCurrent())
+
+ // 1 2 3 ...
+ p = New(31, 10, 2, 3)
+ pages = p.Pages()
+ assert.Len(t, pages, 4)
+ assert.Equal(t, 1, pages[0].Num())
+ assert.False(t, pages[0].IsCurrent())
+ assert.Equal(t, 2, pages[1].Num())
+ assert.True(t, pages[1].IsCurrent())
+ assert.Equal(t, 3, pages[2].Num())
+ assert.False(t, pages[2].IsCurrent())
+ assert.Equal(t, -1, pages[3].Num())
+ assert.False(t, pages[3].IsCurrent())
+
+ // 1 2 3 ...
+ p = New(41, 10, 2, 3)
+ pages = p.Pages()
+ assert.Len(t, pages, 4)
+ assert.Equal(t, 1, pages[0].Num())
+ assert.False(t, pages[0].IsCurrent())
+ assert.Equal(t, 2, pages[1].Num())
+ assert.True(t, pages[1].IsCurrent())
+ assert.Equal(t, 3, pages[2].Num())
+ assert.False(t, pages[2].IsCurrent())
+ assert.Equal(t, -1, pages[3].Num())
+ assert.False(t, pages[3].IsCurrent())
+
+ // 1 2 3 4 5 6 7 ...
+ p = New(100, 10, 1, 7)
+ pages = p.Pages()
+ assert.Len(t, pages, 8)
+ assert.Equal(t, 1, pages[0].Num())
+ assert.True(t, pages[0].IsCurrent())
+ assert.Equal(t, 2, pages[1].Num())
+ assert.False(t, pages[1].IsCurrent())
+ assert.Equal(t, 3, pages[2].Num())
+ assert.False(t, pages[2].IsCurrent())
+ assert.Equal(t, 4, pages[3].Num())
+ assert.False(t, pages[3].IsCurrent())
+ assert.Equal(t, 5, pages[4].Num())
+ assert.False(t, pages[4].IsCurrent())
+ assert.Equal(t, 6, pages[5].Num())
+ assert.False(t, pages[5].IsCurrent())
+ assert.Equal(t, 7, pages[6].Num())
+ assert.False(t, pages[6].IsCurrent())
+ assert.Equal(t, -1, pages[7].Num())
+ assert.False(t, pages[7].IsCurrent())
+
+ // 1 2 3 4 5 6 7 ...
+ p = New(100, 10, 2, 7)
+ pages = p.Pages()
+ assert.Len(t, pages, 8)
+ assert.Equal(t, 1, pages[0].Num())
+ assert.False(t, pages[0].IsCurrent())
+ assert.Equal(t, 2, pages[1].Num())
+ assert.True(t, pages[1].IsCurrent())
+ assert.Equal(t, 3, pages[2].Num())
+ assert.False(t, pages[2].IsCurrent())
+ assert.Equal(t, 4, pages[3].Num())
+ assert.False(t, pages[3].IsCurrent())
+ assert.Equal(t, 5, pages[4].Num())
+ assert.False(t, pages[4].IsCurrent())
+ assert.Equal(t, 6, pages[5].Num())
+ assert.False(t, pages[5].IsCurrent())
+ assert.Equal(t, 7, pages[6].Num())
+ assert.False(t, pages[6].IsCurrent())
+ assert.Equal(t, -1, pages[7].Num())
+ assert.False(t, pages[7].IsCurrent())
+ })
+
+ t.Run("Has both more previous and next pages", func(t *testing.T) {
+ // ... 2 3 ...
+ p := New(35, 10, 2, 2)
+ pages := p.Pages()
+ assert.Len(t, pages, 4)
+ assert.Equal(t, -1, pages[0].Num())
+ assert.False(t, pages[0].IsCurrent())
+ assert.Equal(t, 2, pages[1].Num())
+ assert.True(t, pages[1].IsCurrent())
+ assert.Equal(t, 3, pages[2].Num())
+ assert.False(t, pages[2].IsCurrent())
+ assert.Equal(t, -1, pages[3].Num())
+ assert.False(t, pages[3].IsCurrent())
+
+ // ... 2 3 4 ...
+ p = New(49, 10, 3, 3)
+ pages = p.Pages()
+ assert.Len(t, pages, 5)
+ assert.Equal(t, -1, pages[0].Num())
+ assert.False(t, pages[0].IsCurrent())
+ assert.Equal(t, 2, pages[1].Num())
+ assert.False(t, pages[1].IsCurrent())
+ assert.Equal(t, 3, pages[2].Num())
+ assert.True(t, pages[2].IsCurrent())
+ assert.Equal(t, 4, pages[3].Num())
+ assert.False(t, pages[3].IsCurrent())
+ assert.Equal(t, -1, pages[4].Num())
+ assert.False(t, pages[4].IsCurrent())
+ })
+}