github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/paginator/paginator.go (about)

     1  // Copyright 2023 The GitBundle Inc. All rights reserved.
     2  // Copyright 2017 The Gitea Authors. All rights reserved.
     3  // Use of this source code is governed by a MIT-style
     4  // license that can be found in the LICENSE file.
     5  
     6  // Copyright 2022 The GitBundle Authors.
     7  // Copyright 2015 Unknwon. Licensed under the Apache License, Version 2.0
     8  
     9  package paginator
    10  
    11  /*
    12  In template:
    13  
    14  ```html
    15  {{if not .Page.IsFirst}}[First](1){{end}}
    16  {{if .Page.HasPrevious}}[Previous]({{.Page.Previous}}){{end}}
    17  
    18  {{range .Page.Pages}}
    19  	{{if eq .Num -1}}
    20  	...
    21  	{{else}}
    22  	{{.Num}}{{if .IsCurrent}}(current){{end}}
    23  	{{end}}
    24  {{end}}
    25  
    26  {{if .Page.HasNext}}[Next]({{.Page.Next}}){{end}}
    27  {{if not .Page.IsLast}}[Last]({{.Page.TotalPages}}){{end}}
    28  ```
    29  
    30  Output:
    31  
    32  ```
    33  [First](1) [Previous](2) ... 2 3(current) 4 ... [Next](4) [Last](5)
    34  ```
    35  */
    36  
    37  // Paginator represents a set of results of pagination calculations.
    38  type Paginator struct {
    39  	total     int // total rows count
    40  	pagingNum int // how many rows in one page
    41  	current   int // current page number
    42  	numPages  int // how many pages to show on the UI
    43  }
    44  
    45  // New initialize a new pagination calculation and returns a Paginator as result.
    46  func New(total, pagingNum, current, numPages int) *Paginator {
    47  	if pagingNum <= 0 {
    48  		pagingNum = 1
    49  	}
    50  	if current <= 0 {
    51  		current = 1
    52  	}
    53  	p := &Paginator{total, pagingNum, current, numPages}
    54  	if p.current > p.TotalPages() {
    55  		p.current = p.TotalPages()
    56  	}
    57  	return p
    58  }
    59  
    60  // IsFirst returns true if current page is the first page.
    61  func (p *Paginator) IsFirst() bool {
    62  	return p.current == 1
    63  }
    64  
    65  // HasPrevious returns true if there is a previous page relative to current page.
    66  func (p *Paginator) HasPrevious() bool {
    67  	return p.current > 1
    68  }
    69  
    70  func (p *Paginator) Previous() int {
    71  	if !p.HasPrevious() {
    72  		return p.current
    73  	}
    74  	return p.current - 1
    75  }
    76  
    77  // HasNext returns true if there is a next page relative to current page.
    78  func (p *Paginator) HasNext() bool {
    79  	return p.total > p.current*p.pagingNum
    80  }
    81  
    82  func (p *Paginator) Next() int {
    83  	if !p.HasNext() {
    84  		return p.current
    85  	}
    86  	return p.current + 1
    87  }
    88  
    89  // IsLast returns true if current page is the last page.
    90  func (p *Paginator) IsLast() bool {
    91  	if p.total == 0 {
    92  		return true
    93  	}
    94  	return p.total > (p.current-1)*p.pagingNum && !p.HasNext()
    95  }
    96  
    97  // Total returns number of total rows.
    98  func (p *Paginator) Total() int {
    99  	return p.total
   100  }
   101  
   102  // TotalPages returns number of total pages.
   103  func (p *Paginator) TotalPages() int {
   104  	if p.total == 0 {
   105  		return 1
   106  	}
   107  	return (p.total + p.pagingNum - 1) / p.pagingNum
   108  }
   109  
   110  // Current returns current page number.
   111  func (p *Paginator) Current() int {
   112  	return p.current
   113  }
   114  
   115  // PagingNum returns number of page size.
   116  func (p *Paginator) PagingNum() int {
   117  	return p.pagingNum
   118  }
   119  
   120  // Page presents a page in the paginator.
   121  type Page struct {
   122  	num       int
   123  	isCurrent bool
   124  }
   125  
   126  func (p *Page) Num() int {
   127  	return p.num
   128  }
   129  
   130  func (p *Page) IsCurrent() bool {
   131  	return p.isCurrent
   132  }
   133  
   134  func getMiddleIdx(numPages int) int {
   135  	return (numPages + 1) / 2
   136  }
   137  
   138  // Pages returns a list of nearby page numbers relative to current page.
   139  // If value is -1 means "..." that more pages are not showing.
   140  func (p *Paginator) Pages() []*Page {
   141  	if p.numPages == 0 {
   142  		return []*Page{}
   143  	} else if p.numPages == 1 && p.TotalPages() == 1 {
   144  		// Only show current page.
   145  		return []*Page{{1, true}}
   146  	}
   147  
   148  	// Total page number is less or equal.
   149  	if p.TotalPages() <= p.numPages {
   150  		pages := make([]*Page, p.TotalPages())
   151  		for i := range pages {
   152  			pages[i] = &Page{i + 1, i+1 == p.current}
   153  		}
   154  		return pages
   155  	}
   156  
   157  	numPages := p.numPages
   158  	offsetIdx := 0
   159  	hasMoreNext := false
   160  
   161  	// Check more previous and next pages.
   162  	previousNum := getMiddleIdx(p.numPages) - 1
   163  	if previousNum > p.current-1 {
   164  		previousNum -= previousNum - (p.current - 1)
   165  	}
   166  	nextNum := p.numPages - previousNum - 1
   167  	if p.current+nextNum > p.TotalPages() {
   168  		delta := nextNum - (p.TotalPages() - p.current)
   169  		nextNum -= delta
   170  		previousNum += delta
   171  	}
   172  
   173  	offsetVal := p.current - previousNum
   174  	if offsetVal > 1 {
   175  		numPages++
   176  		offsetIdx = 1
   177  	}
   178  
   179  	if p.current+nextNum < p.TotalPages() {
   180  		numPages++
   181  		hasMoreNext = true
   182  	}
   183  
   184  	pages := make([]*Page, numPages)
   185  
   186  	// There are more previous pages.
   187  	if offsetIdx == 1 {
   188  		pages[0] = &Page{-1, false}
   189  	}
   190  	// There are more next pages.
   191  	if hasMoreNext {
   192  		pages[len(pages)-1] = &Page{-1, false}
   193  	}
   194  
   195  	// Check previous pages.
   196  	for i := 0; i < previousNum; i++ {
   197  		pages[offsetIdx+i] = &Page{i + offsetVal, false}
   198  	}
   199  
   200  	pages[offsetIdx+previousNum] = &Page{p.current, true}
   201  
   202  	// Check next pages.
   203  	for i := 1; i <= nextNum; i++ {
   204  		pages[offsetIdx+previousNum+i] = &Page{p.current + i, false}
   205  	}
   206  
   207  	return pages
   208  }