mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
221 lines
5.0 KiB
Go
221 lines
5.0 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// Copyright 2015 https://github.com/unknwon. Licensed under the Apache License, Version 2.0
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package paginator
|
|
|
|
import "github.com/hanzoai/git/modules/util"
|
|
|
|
/*
|
|
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, -1 means unknown
|
|
totalPages int // total pages count, -1 means unknown
|
|
current int // current page number
|
|
|
|
pagingNum int // how many rows in one page
|
|
numPages int // how many pages to show on the UI
|
|
|
|
hasNext *bool // used for total=-1 ("unlimited paging")
|
|
}
|
|
|
|
// New initialize a new pagination calculation and returns a Paginator as result.
|
|
func New(total, pagingNum, current, numPages int) *Paginator {
|
|
pagingNum = max(pagingNum, 1)
|
|
totalPages := util.Iif(total == -1, -1, (total+pagingNum-1)/pagingNum)
|
|
if total >= 0 {
|
|
current = min(current, totalPages)
|
|
}
|
|
current = max(current, 1)
|
|
return &Paginator{
|
|
total: total,
|
|
totalPages: totalPages,
|
|
current: current,
|
|
pagingNum: pagingNum,
|
|
numPages: numPages,
|
|
}
|
|
}
|
|
|
|
func (p *Paginator) SetUnlimitedPaging(curRows int, hasNext bool) {
|
|
// For "unlimited paging", we need to know the rows of current page to determine if there is a next page.
|
|
p.hasNext = &hasNext
|
|
if p.total == -1 && p.current == 1 && !hasNext {
|
|
// if there is only one page for the "unlimited paging", set total rows/pages count
|
|
// then the tmpl could decide to hide the nav bar.
|
|
p.total = curRows
|
|
p.totalPages = util.Iif(p.total == 0, 0, 1)
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
if p.hasNext != nil {
|
|
return *p.hasNext
|
|
}
|
|
return p.current*p.pagingNum < p.total
|
|
}
|
|
|
|
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 {
|
|
return !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 {
|
|
return p.totalPages
|
|
}
|
|
|
|
// 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 nil
|
|
} else if p.total == -1 || (p.numPages == 1 && p.TotalPages() == 1) {
|
|
// Only show current page.
|
|
return []*Page{{p.current, 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
|
|
}
|